Streamdown Recharts
A Streamdown custom renderer that turns recharts-json code fences from an LLM stream into live, interactive charts — with table view and CSV/XLSX/PNG export.
On this page
Large language models are great at producing structured JSON, but turning that
JSON into a chart usually means shipping a brittle eval or a heavy client
bundle. Streamdown Recharts takes a different route: the model emits a
fenced recharts-json block, and a Streamdown custom renderer parses
it, validates it with Zod, and renders a Recharts chart
— streaming-aware, so you get a loading skeleton until the fence closes.
Reported vs. Adjusted EBIT (in € millions).
It handles bar, line, area, pie and scatter charts, falls back gracefully on incomplete or malformed JSON, and ships a table view plus one-click exports.
Installation
npx shadcn@latest add https://bitbasti.com/r/streamdown-recharts.jsonThen register the renderer on your Streamdown instance via the renderers
plugin slot. This is the minimal wiring you need:
import { Streamdown } from "streamdown";
import { rechartsRenderers } from "@/components/streamdown-recharts";
export function Message({ markdown }: { markdown: string }) {
return (
<Streamdown plugins={{ renderers: rechartsRenderers }}>
{markdown}
</Streamdown>
);
}rechartsRenderers is a CustomRenderer[] that maps the recharts-json (and
rechart-json) code-fence languages to the chart component. You can combine it
with Streamdown's other plugins (code, math, mermaid, cjk) in the same
object:
<Streamdown
plugins={{
code,
math,
mermaid,
renderers: rechartsRenderers,
}}
>
{markdown}
</Streamdown>Usage
Anywhere in the model's markdown, a fenced recharts-json block is
upgraded to a chart:
```recharts-json
{
"chartType": "line",
"xKey": "month",
"series": [{ "dataKey": "visitors", "label": "Visitors" }],
"data": [
{ "month": "Jan", "visitors": 5000 },
{ "month": "Feb", "visitors": 6200 }
]
}
```While the fence is still streaming, Streamdown reports the block as incomplete and the renderer shows a skeleton instead of flashing a parse error. Once the closing fence arrives, the JSON is parsed and validated in one pass.
Prompting the model
To get the model to emit charts, teach it the fence in your system prompt and let it decide when a chart helps. A compact system prompt:
You are a data analyst assistant. When the user asks for a chart, or when
tabular numbers would be clearer as a visualization, respond with a fenced
`recharts-json` code block.
The JSON must match this shape:
- chartType: "bar" | "line" | "area" | "pie" | "scatter"
- xKey: string (the category/value axis key; omit for pie)
- series: array of { dataKey, label } (one entry per plotted value)
- data: array of row objects keyed by xKey and each series dataKey
- nameKey / valueKey: strings (pie charts only)
- meta: { title, description } for the chart header
Emit only valid JSON inside the fence — no comments or trailing commas. Use
null for missing data points. Add a short sentence of context before the chart.A sample user prompt:
Plot our quarterly revenue and costs for last year:
Q1 revenue 120k / costs 90k, Q2 185k / 120k, Q3 240k / 150k, Q4 310k / 190k.
Use an area chart in US dollars.The model replies with prose plus a recharts-json fence, which the renderer
upgrades into the live chart above.
Examples
Line
Multiple series share a single x-axis. String x-values render as categories, numeric ones as a continuous axis.
Monthly traffic trend.
Area
Stack several series to compare trends — here, two filled areas on a shared axis.
Revenue and costs by quarter.
Pie
Pie charts are driven by nameKey (slice label) and valueKey (slice size).
Share by deployment model.
Scatter
The x-axis key is inferred from the data when xKey is omitted, and the axis
type adapts to whether the values are numeric.
Advertising investment against sales revenue.
Schema
Every spec is validated against a Zod schema before it renders. Invalid specs show the raw JSON with an inline error rather than crashing the page.
| Field | Type | Required | Notes |
|---|---|---|---|
chartType | "bar" | "line" | "area" | "pie" | "scatter" | yes | Selects the visualization. |
series | { dataKey, label?, valueFormat?, ... }[] | yes | At least one series. |
data | Record<string, string | number | null>[] | yes | null renders as a gap. |
xKey | string | no | Category/value axis key. Inferred for scatter. |
nameKey | string | no | Pie slice label key. |
valueKey | string | no | Pie slice value key. |
layout | "horizontal" | "vertical" | no | Bar charts only. |
locale | string | no | Number/tick formatting, e.g. de-DE. |
currency | string | no | ISO currency for valueFormat: "currency". |
meta | { title?, description? } | no | Header above the chart and export file name. |
Beyond the chart
Each rendered chart carries a small toolbar:
- Table view — toggle from chart to a formatted data table.
- Export — download the data as CSV or XLSX, or the chart as a PNG.
Number formatting respects the spec's locale and currency, so the same data
reads naturally whether you are in en-US or de-DE.