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

推荐订阅源

MongoDB | Blog
MongoDB | Blog
Recorded Future
Recorded Future
Jina AI
Jina AI
The Register - Security
The Register - Security
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
月光博客
月光博客
博客园 - 三生石上(FineUI控件)
F
Fortinet All Blogs
人人都是产品经理
人人都是产品经理
S
SegmentFault 最新的问题
Apple Machine Learning Research
Apple Machine Learning Research
L
LangChain Blog
Y
Y Combinator Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
GbyAI
GbyAI
The GitHub Blog
The GitHub Blog
Vercel News
Vercel News
博客园 - 【当耐特】
雷峰网
雷峰网
The Cloudflare Blog
阮一峰的网络日志
阮一峰的网络日志
aimingoo的专栏
aimingoo的专栏
云风的 BLOG
云风的 BLOG
I
InfoQ
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Google DeepMind News
Google DeepMind News
Security Latest
Security Latest
有赞技术团队
有赞技术团队
L
Lohrmann on Cybersecurity
P
Proofpoint News Feed
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
The Last Watchdog
The Last Watchdog
P
Privacy & Cybersecurity Law Blog
Scott Helme
Scott Helme
Google Online Security Blog
Google Online Security Blog
WordPress大学
WordPress大学
Hacker News - Newest:
Hacker News - Newest: "LLM"
NISL@THU
NISL@THU
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
B
Blog RSS Feed
Cyberwarzone
Cyberwarzone
K
Kaspersky official blog
F
Full Disclosure
Martin Fowler
Martin Fowler
Spread Privacy
Spread Privacy
D
Docker
C
Cisco Blogs
www.infosecurity-magazine.com
www.infosecurity-magazine.com
H
Hacker News: Front Page

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[];
}