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

推荐订阅源

S
Secure Thoughts
P
Privacy International News Feed
T
Tenable Blog
L
Lohrmann on Cybersecurity
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
T
Threat Research - Cisco Blogs
S
Securelist
C
CXSECURITY Database RSS Feed - CXSecurity.com
Cisco Talos Blog
Cisco Talos Blog
T
The Exploit Database - CXSecurity.com
S
Schneier on Security
P
Privacy & Cybersecurity Law Blog
Vercel News
Vercel News
Cyberwarzone
Cyberwarzone
月光博客
月光博客
T
The Blog of Author Tim Ferriss
Scott Helme
Scott Helme
爱范儿
爱范儿
Stack Overflow Blog
Stack Overflow Blog
C
Cisco Blogs
aimingoo的专栏
aimingoo的专栏
博客园 - 司徒正美
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
P
Proofpoint News Feed
A
Arctic Wolf
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
L
LangChain Blog
C
Cyber Attacks, Cyber Crime and Cyber Security
阮一峰的网络日志
阮一峰的网络日志
Simon Willison's Weblog
Simon Willison's Weblog
T
Tor Project blog
Security Latest
Security Latest
Blog — PlanetScale
Blog — PlanetScale
G
GRAHAM CLULEY
V
Vulnerabilities – Threatpost
博客园 - 三生石上(FineUI控件)
I
InfoQ
Spread Privacy
Spread Privacy
B
Blog RSS Feed
Microsoft Azure Blog
Microsoft Azure Blog
S
SegmentFault 最新的问题
云风的 BLOG
云风的 BLOG
Last Week in AI
Last Week in AI
MongoDB | Blog
MongoDB | Blog
C
CERT Recently Published Vulnerability Notes
A
About on SuperTechFans
博客园_首页
Engineering at Meta
Engineering at Meta
Project Zero
Project Zero
Latest news
Latest news

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.