惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

量子位
C
CXSECURITY Database RSS Feed - CXSecurity.com
S
Schneier on Security
博客园 - 叶小钗
博客园 - 三生石上(FineUI控件)
C
Cybersecurity and Infrastructure Security Agency CISA
Engineering at Meta
Engineering at Meta
Google DeepMind News
Google DeepMind News
酷 壳 – CoolShell
酷 壳 – CoolShell
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
博客园_首页
T
Threat Research - Cisco Blogs
C
Cisco Blogs
Recent Announcements
Recent Announcements
S
Securelist
N
Netflix TechBlog - Medium
The Register - Security
The Register - Security
P
Privacy & Cybersecurity Law Blog
宝玉的分享
宝玉的分享
D
Darknet – Hacking Tools, Hacker News & Cyber Security
L
LINUX DO - 热门话题
T
Tor Project blog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
月光博客
月光博客
AWS News Blog
AWS News Blog
P
Proofpoint News Feed
博客园 - 司徒正美
L
LINUX DO - 最新话题
Stack Overflow Blog
Stack Overflow Blog
博客园 - 聂微东
H
Help Net Security
Spread Privacy
Spread Privacy
PCI Perspectives
PCI Perspectives
Project Zero
Project Zero
I
Intezer
T
The Blog of Author Tim Ferriss
有赞技术团队
有赞技术团队
The Last Watchdog
The Last Watchdog
C
Check Point Blog
Blog — PlanetScale
Blog — PlanetScale
B
Blog RSS Feed
MyScale Blog
MyScale Blog
V
Vulnerabilities – Threatpost
Recorded Future
Recorded Future
T
Tenable Blog
Jina AI
Jina AI
D
DataBreaches.Net
阮一峰的网络日志
阮一峰的网络日志

Streamdown Documentation

Configuration FAQ Getting Started Introduction Security Animation Carets Code Blocks GitHub Flavored Markdown Interactivity Internationalization Link Safety Memoization Migrate from react-markdown Styling Unterminated Block Parsing Typography Usage @streamdown/cjk @streamdown/code Built-in Plugins @streamdown/math @streamdown/mermaid Custom renderers
Components
Vercel · 2026-04-11 · via Streamdown Documentation

Learn how to customize and extend Streamdown with custom component overrides.

Streamdown allows you to replace any Markdown element with your own React component while maintaining all of Streamdown's functionality.

Pass custom components using the components prop:

<Streamdown
  components={{
    h1: ({ children }) => (
      <h1 className="text-4xl font-bold text-blue-600">
        {children}
      </h1>
    ),
    h2: ({ children }) => (
      <h2 className="text-3xl font-semibold text-blue-500">
        {children}
      </h2>
    ),
    p: ({ children }) => (
      <p className="text-gray-700 leading-relaxed">
        {children}
      </p>
    ),
  }}
>
  {markdown}
</Streamdown>

You can override any of the following standard HTML components:

  • Headings: h1, h2, h3, h4, h5, h6
  • Text: p, strong, em
  • Lists: ul, ol, li
  • Links: a
  • Code: code, pre
  • Quotes: blockquote
  • Tables: table, thead, tbody, tr, th, td
  • Media: img
  • Other: hr, sup, sub, section

Custom components receive all the props that the default components would receive, including:

  • children - The content to render
  • className - CSS class names from the Markdown AST (if applicable)
  • node - The Markdown AST node (for advanced use cases)
  • Element-specific props (e.g., href for links, src for images)

Custom components fully replace the default implementations, including their built-in Tailwind styles. The className prop only contains classes from the Markdown AST (e.g., language-js on code elements) — it does not include the default styles that Streamdown normally applies.

If you need to preserve the default appearance, you must re-apply the styles yourself. See the Styling documentation for the default classes, or use CSS selectors with data-streamdown attributes instead of component overrides when you only need visual changes.

For example, the default h2 component applies mt-6 mb-2 font-semibold text-2xl. When you override it, those styles are lost unless you include them:

<Streamdown
  components={{
    // ❌ Loses default spacing and font styles
    h2: ({ children }) => (
      <h2 className="text-blue-500">{children}</h2>
    ),
    // ✅ Preserves default styles alongside custom ones
    h2: ({ children, className }) => (
      <h2 className={`mt-6 mb-2 font-semibold text-2xl text-blue-500 ${className ?? ''}`}>
        {children}
      </h2>
    ),
  }}
>
  {markdown}
</Streamdown>

When you override components.code, you replace the entire code rendering pipeline — inline code, block code with syntax highlighting, mermaid diagrams, and custom renderers. To customize only inline code without affecting block code, use the inlineCode virtual component:

<Streamdown
  components={{
    inlineCode: ({ children }) => (
      <code className="rounded bg-violet-100 px-1.5 py-0.5 text-violet-800 text-sm">
        {children}
      </code>
    ),
  }}
>
  {markdown}
</Streamdown>

Block code blocks, syntax highlighting, and mermaid diagrams continue to work normally. You can also combine inlineCode with a custom code component — inlineCode handles inline spans while code handles fenced code blocks:

<Streamdown
  components={{
    inlineCode: ({ children }) => (
      <code className="bg-violet-100 text-violet-800 rounded px-1 text-sm">
        {children}
      </code>
    ),
    code: MyCustomCodeBlock,
  }}
>
  {markdown}
</Streamdown>

When streaming markdown, custom components can detect if their code fence is still being streamed using the useIsCodeFenceIncomplete hook. This is useful for expensive-to-render components where you want to show a loading state until the code block is complete.

import { Streamdown, useIsCodeFenceIncomplete } from "streamdown";

const MyCodeBlock = ({ children }) => {
  const isIncomplete = useIsCodeFenceIncomplete();

  if (isIncomplete) {
    return <div className="animate-pulse bg-muted h-24 rounded" />;
  }

  return <pre><code>{children}</code></pre>;
};

export default function Page() {
  return (
    <Streamdown
      components={{ code: MyCodeBlock }}
      isAnimating={isStreaming}
    >
      {markdown}
    </Streamdown>
  );
}

The hook returns true when all of the following are true:

  • isAnimating={true} (streaming mode is active)
  • The component is in the last block being streamed
  • That block has an unclosed code fence (``` without a closing ```)

Once the code fence closes, the hook returns false and your component can render normally—even while the rest of the markdown continues streaming.

This is particularly useful for Mermaid diagrams and syntax highlighters where continuous re-rendering during streaming would cause performance issues.

<Streamdown
  components={{
    a: ({ href, children, ...props }) => (
      <a
        href={href}
        className="text-purple-600 hover:text-purple-800 underline"
        {...props}
      >
        {children}
      </a>
    ),
  }}
>
  {markdown}
</Streamdown>

When overriding the table component, the built-in copy and download buttons are lost because custom components fully replace default implementations. To restore them, import the table action components and include them in your custom table:

import {
  Streamdown,
  TableCopyDropdown,
  TableDownloadDropdown,
} from "streamdown";

<Streamdown
  components={{
    table: ({ children, className }) => (
      <div data-streamdown="table-wrapper">
        <div className="flex items-center justify-end gap-1">
          <TableCopyDropdown />
          <TableDownloadDropdown />
        </div>
        <MyCustomTable className={className}>{children}</MyCustomTable>
      </div>
    ),
  }}
>
  {markdown}
</Streamdown>

The data-streamdown="table-wrapper" attribute is required — the action components use .closest() to find this wrapper, then .querySelector("table") to locate the <table> element inside it. Your custom table component must render a <table> element as a descendant of the wrapper div.

A TableDownloadButton component is also available for rendering a single-format download button instead of a dropdown:

import { TableDownloadButton } from "streamdown";

<TableDownloadButton format="csv" />

Lower-level utilities

For fully custom implementations, use the extraction and conversion utilities directly:

import {
  extractTableDataFromElement,
  tableDataToCSV,
  tableDataToTSV,
  tableDataToMarkdown,
} from "streamdown";

// Extract structured data from a <table> DOM element
const data = extractTableDataFromElement(tableElement);

// Convert to various formats
const csv = tableDataToCSV(data);
const tsv = tableDataToTSV(data);
const markdown = tableDataToMarkdown(data);

You can render custom HTML tags from AI responses (like <source>, <mention>, etc.) using the allowedTags prop alongside components. This is useful when you instruct the AI to output structured data that renders as interactive components.

For example, you might add a system prompt:

When referencing a source, use: <source id="123">Source Title</source>

The AI then outputs markdown containing:

According to the documentation <source id="abc">Getting Started Guide</source>, you should...

Setup

Use the allowedTags prop to specify which custom tags and attributes to allow through sanitization, then map them to React components:

<Streamdown
  allowedTags={{
    source: ["id"],  // Allow <source> tag with id attribute
  }}
  components={{
    source: ({ id, children }) => (
      <button
        onClick={() => console.log(`Navigate to source: ${id}`)}
        className="text-blue-600 underline cursor-pointer"
      >
        {children}
      </button>
    ),
  }}
>
  {markdown}
</Streamdown>

Multiple Custom Tags

You can allow multiple custom tags:

<Streamdown
  allowedTags={{
    source: ["id"],
    mention: ["user_id", "type"],
    action: ["name", "payload"],
  }}
  components={{
    source: ({ id, children }) => (
      <SourceBadge sourceId={id as string}>{children}</SourceBadge>
    ),
    mention: ({ user_id, children }) => (
      <UserMention userId={user_id as string}>{children}</UserMention>
    ),
    action: ({ name, payload, children }) => (
      <ActionButton name={name as string} payload={payload as string}>
        {children}
      </ActionButton>
    ),
  }}
>
  {markdown}
</Streamdown>

Data Attributes

Use data* in the attributes array to allow all data-* attributes on a tag:

<Streamdown
  allowedTags={{
    widget: ["data*"],  // Allow all data-* attributes
  }}
  components={{
    widget: (props) => <Widget {...props} />,
  }}
>
  {markdown}
</Streamdown>

Important Notes

  • Without allowedTags, custom tags are stripped by the sanitizer (content is preserved, tags are removed)
  • Only attributes listed in allowedTags are preserved; unlisted attributes are stripped
  • The allowedTags prop only works with the default rehype plugins

If you provide custom rehypePlugins, you'll need to configure rehype-sanitize yourself to allow custom tags. See the Security documentation for details.

Plain text tag content

By default, children of custom HTML tags are parsed as markdown. This means underscores, asterisks, and other markdown metacharacters in tag content get formatted unexpectedly — for example, <mention>some_user_name</mention> renders with italicized text instead of a literal underscore.

Use the literalTagContent prop to treat the children of specific tags as plain text:

<Streamdown
  allowedTags={{
    mention: ["user_id"],
  }}
  literalTagContent={["mention"]}
  components={{
    mention: ({ user_id, children }) => (
      <span className="text-blue-600">@{children}</span>
    ),
  }}
>
  {markdown}
</Streamdown>

Tags listed in literalTagContent must also be listed in allowedTags. Otherwise the tag is stripped by the sanitizer before literal content handling applies.

Security Considerations

When allowing custom HTML tags:

  • Only whitelist tags you explicitly need
  • Only whitelist attributes you explicitly need
  • Validate attribute values in your component before using them
  • Never allow script, style, or event handler attributes (onclick, etc.)

See the Security documentation for more details on HTML handling.