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

推荐订阅源

N
News and Events Feed by Topic
WordPress大学
WordPress大学
Vercel News
Vercel News
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
小众软件
小众软件
L
LangChain Blog
雷峰网
雷峰网
D
DataBreaches.Net
博客园 - 三生石上(FineUI控件)
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
T
Tor Project blog
NISL@THU
NISL@THU
Scott Helme
Scott Helme
量子位
S
Security Affairs
T
Threat Research - Cisco Blogs
博客园_首页
云风的 BLOG
云风的 BLOG
D
Docker
AWS News Blog
AWS News Blog
腾讯CDC
博客园 - 聂微东
The GitHub Blog
The GitHub Blog
U
Unit 42
Recent Announcements
Recent Announcements
Apple Machine Learning Research
Apple Machine Learning Research
G
Google Developers Blog
T
The Exploit Database - CXSecurity.com
MongoDB | Blog
MongoDB | Blog
Stack Overflow Blog
Stack Overflow Blog
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
L
LINUX DO - 热门话题
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
The Last Watchdog
The Last Watchdog
C
Cybersecurity and Infrastructure Security Agency CISA
IT之家
IT之家
W
WeLiveSecurity
P
Privacy & Cybersecurity Law Blog
F
Full Disclosure
L
Lohrmann on Cybersecurity
The Hacker News
The Hacker News
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
Y
Y Combinator Blog
S
Security @ Cisco Blogs
C
Cyber Attacks, Cyber Crime and Cyber Security
C
Check Point Blog
C
CXSECURITY Database RSS Feed - CXSecurity.com
N
News and Events Feed by Topic
PCI Perspectives
PCI Perspectives
I
InfoQ

Streamdown Documentation

Configuration FAQ Getting Started Introduction Security Animation Carets Code Blocks Components 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
Vercel · 2026-04-11 · via Streamdown Documentation

Register custom renderers for arbitrary code fence languages.

The renderers field on PluginConfig lets you register custom React components for arbitrary code fence languages. Use this to render Vega-Lite charts, AntV infographics, D2 diagrams, PlantUML, or any other visualization — without forking Streamdown.

Custom renderers take priority over default code blocks. If a custom renderer matches a language, it renders instead of the default CodeBlock. You can even override mermaid by registering a renderer for the "mermaid" language.

Pass an array of { language, component } objects to plugins.renderers:

import { Streamdown } from "streamdown";
import type { CustomRendererProps } from "streamdown";
import { VegaLiteRenderer } from "./vega-lite-renderer";

export default function Chat() {
  return (
    <Streamdown
      plugins={{
        renderers: [
          { language: "vega-lite", component: VegaLiteRenderer },
        ],
      }}
    >
      {markdown}
    </Streamdown>
  );
}

The language field accepts a string or an array of strings:

const renderers = [
  { language: ["vega", "vega-lite"], component: VegaLiteRenderer },
  { language: "infographic", component: InfographicRenderer },
  { language: "d2", component: D2Renderer },
];

<Streamdown plugins={{ renderers }}>{markdown}</Streamdown>

Every custom renderer receives these props:

PropTypeDescription
codestringThe raw text content inside the code fence
languagestringThe language identifier from the code fence
isIncompletebooleantrue while the code fence is still being streamed

Streamdown exports its internal code block components so your custom renderers can reuse them for consistent styling:

import { useEffect, useRef } from "react";
import type { CustomRendererProps } from "streamdown";
import { CodeBlockContainer, CodeBlockHeader } from "streamdown";

export const VegaLiteRenderer = ({
  code,
  language,
  isIncomplete,
}: CustomRendererProps) => {
  const containerRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    if (isIncomplete || !containerRef.current) {
      return;
    }

    let cancelled = false;

    const render = async () => {
      const spec = JSON.parse(code);
      const vegaEmbed = (await import("vega-embed")).default;

      if (cancelled || !containerRef.current) {
        return;
      }

      containerRef.current.innerHTML = "";
      await vegaEmbed(containerRef.current, spec, {
        actions: false,
        renderer: "svg",
      });
    };

    render();

    return () => {
      cancelled = true;
    };
  }, [code, isIncomplete]);

  return (
    <CodeBlockContainer isIncomplete={isIncomplete} language={language}>
      <CodeBlockHeader language={language} />
      {isIncomplete ? (
        <div className="flex h-48 items-center justify-center rounded-md bg-muted">
          <span className="text-muted-foreground text-sm">
            Loading chart...
          </span>
        </div>
      ) : (
        <div ref={containerRef} className="overflow-hidden rounded-md p-4" />
      )}
    </CodeBlockContainer>
  );
};

Exported components

ComponentDescription
CodeBlockFull code block with syntax highlighting and controls
CodeBlockContainerOuter wrapper with border and styling
CodeBlockHeaderLanguage label header
CodeBlockCopyButtonCopy-to-clipboard button
CodeBlockDownloadButtonDownload button
CodeBlockSkeletonLoading skeleton placeholder

Vega-Lite charts

Vega-Lite is a grammar for interactive graphics. Install vega, vega-lite, and vega-embed, then create a renderer that parses the JSON spec and calls vegaEmbed:

npm install vega vega-lite vega-embed

The renderer from the reusing built-in components section above is a complete Vega-Lite implementation. Once registered, your AI can output charts like:

```vega-lite
{
  "$schema": "https://vega.github.io/schema/vega-lite/v5.json",
  "width": "container",
  "height": 200,
  "data": {
    "values": [
      {"month": "Jan", "revenue": 28},
      {"month": "Feb", "revenue": 55},
      {"month": "Mar", "revenue": 43}
    ]
  },
  "mark": "bar",
  "encoding": {
    "x": {"field": "month", "type": "nominal"},
    "y": {"field": "revenue", "type": "quantitative"}
  }
}
```

AntV Infographic

AntV Infographic renders rich infographic diagrams from a YAML-like DSL that supports streaming output. Install the package, then create a renderer:

npm install @antv/infographic
import { useEffect, useRef } from "react";
import type { CustomRendererProps } from "streamdown";
import { CodeBlockContainer, CodeBlockHeader } from "streamdown";

export const InfographicRenderer = ({
  code,
  language,
  isIncomplete,
}: CustomRendererProps) => {
  const containerRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    if (!containerRef.current) {
      return;
    }

    let cancelled = false;

    const render = async () => {
      const { Infographic } = await import("@antv/infographic");

      if (cancelled || !containerRef.current) {
        return;
      }

      containerRef.current.innerHTML = "";
      new Infographic({
        container: containerRef.current,
        text: code,
      });
    };

    render();

    return () => {
      cancelled = true;
    };
  }, [code]);

  return (
    <CodeBlockContainer isIncomplete={isIncomplete} language={language}>
      <CodeBlockHeader language={language} />
      <div ref={containerRef} className="overflow-hidden rounded-md" />
    </CodeBlockContainer>
  );
};

Register it alongside other renderers:

import { Streamdown } from "streamdown";
import { InfographicRenderer } from "./infographic-renderer";
import { VegaLiteRenderer } from "./vega-lite-renderer";

<Streamdown
  plugins={{
    renderers: [
      { language: ["vega", "vega-lite"], component: VegaLiteRenderer },
      { language: "infographic", component: InfographicRenderer },
    ],
  }}
>
  {markdown}
</Streamdown>

Your AI can then output infographics using the AntV DSL:

```infographic
infographic list-row-horizontal-icon-arrow
data
  title Product Development Lifecycle
  desc Complete process from requirements to launch
  items
    - label Research
      value 15
      desc User interviews and competitive analysis
      icon mdi/account-search
    - label Design
      value 42
      desc Interaction prototype and visual design
      icon mdi/palette
    - label Development
      value 65
      desc Implementation and testing
      icon mdi/code-tags
    - label Launch
      value 100
      desc Official release and user feedback
      icon mdi/rocket-launch
```

Since AntV Infographic supports streaming natively, you can skip the isIncomplete guard and render progressively as the code fence streams in.

D2 diagrams

D2 is a declarative diagramming language. Since D2 compiles to SVG server-side, you can render it with an API call:

import { useEffect, useRef } from "react";
import type { CustomRendererProps } from "streamdown";
import { CodeBlockContainer, CodeBlockHeader } from "streamdown";

export const D2Renderer = ({
  code,
  language,
  isIncomplete,
}: CustomRendererProps) => {
  const containerRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    if (isIncomplete || !containerRef.current) {
      return;
    }

    let cancelled = false;

    const render = async () => {
      const response = await fetch("/api/d2", {
        method: "POST",
        body: code,
      });

      if (cancelled || !containerRef.current) {
        return;
      }

      const svg = await response.text();
      containerRef.current.innerHTML = svg;
    };

    render();

    return () => {
      cancelled = true;
    };
  }, [code, isIncomplete]);

  return (
    <CodeBlockContainer isIncomplete={isIncomplete} language={language}>
      <CodeBlockHeader language={language} />
      {isIncomplete ? (
        <div className="flex h-48 items-center justify-center rounded-md bg-muted">
          <span className="text-muted-foreground text-sm">
            Loading diagram...
          </span>
        </div>
      ) : (
        <div ref={containerRef} className="overflow-hidden rounded-md p-4" />
      )}
    </CodeBlockContainer>
  );
};

During streaming, isIncomplete is true while the code fence is still being written. For most renderers, show a loading placeholder and wait for the complete spec:

const MyRenderer = ({ code, isIncomplete }: CustomRendererProps) => {
  if (isIncomplete) {
    return (
      <div className="flex h-48 items-center justify-center rounded-md bg-muted">
        <span className="text-muted-foreground text-sm">Loading...</span>
      </div>
    );
  }

  return <MyVisualization data={code} />;
};

Some libraries like AntV Infographic support progressive rendering — in those cases, you can render on every update and skip the isIncomplete guard.

Custom renderers work alongside all other plugins. The rendering priority is:

  1. Custom renderers (checked first)
  2. Mermaid plugin (if configured)
  3. Default code block with syntax highlighting
import { mermaid } from "@streamdown/mermaid";
import { code } from "@streamdown/code";

<Streamdown
  plugins={{
    code,
    mermaid,
    renderers: [
      { language: "vega-lite", component: VegaLiteRenderer },
      { language: "infographic", component: InfographicRenderer },
    ],
  }}
>
  {markdown}
</Streamdown>
interface CustomRendererProps {
  code: string;
  language: string;
  isIncomplete: boolean;
}

interface CustomRenderer {
  language: string | string[];
  component: React.ComponentType<CustomRendererProps>;
}

interface PluginConfig {
  // ...existing fields
  renderers?: CustomRenderer[];
}