React and Streamdown
A React component draws a plot block, and a ready renderer plugs it into Streamdown, so a chat that streams Markdown shows a placeholder while a block streams and the chart once its fence closes.
Install
npm i @devlooped/plotdown
React 18 or later; for Streamdown, streamdown 2.7 or later.
Streamdown
import {Streamdown} from "streamdown";
import {plotRenderer} from "@devlooped/plotdown/streamdown";
const plugins = {renderers: [plotRenderer]};
<Streamdown plugins={plugins} isAnimating={streaming}>{markdown}</Streamdown>
The renderer takes plot, oplot and observable-plot blocks. A plot block whose text does not start with { (a gnuplot script) stays a Streamdown code block. For options, make one with createPlotRenderer:
import {createPlotRenderer} from "@devlooped/plotdown/streamdown";
const plugins = {renderers: [createPlotRenderer({allowRemote: ["https://data.example"]})]};
Define plugins once, outside the component, so Streamdown does not see a new object on each render.
<Plotdown>
For your own Markdown pipeline, or a block you hold as a string or an object:
import {Plotdown} from "@devlooped/plotdown/react";
<Plotdown code={source} isIncomplete={streaming} />
| Prop | |
|---|---|
code |
The block, as text or as a parsed object |
isIncomplete |
Still streaming: show the placeholder, never an error |
placeholder |
What to show meanwhile (default: "Drawing chart…") |
allowRemote, baseUrl, limits, fetch |
Remote data, off by default |
onError |
Called with the PlotdownError of a block that fails |
className |
Added to the outer div.plotdown |
The chart is drawn in an effect, on the client: on the server and in the first paint the component holds the placeholder, so server rendering sends no chart and no mismatch. Both modules are marked "use client", so a server component of the Next.js App Router can import and render them. A block that fails shows its source and the reason (.plotdown-error, .plotdown-message); the chart is removed when the component unmounts.
Try it
Ask your model for a chart in a ```plot fence (the agent skill teaches it the format), or paste this into a message:
```plot
{
"y": {"grid": true},
"marks": [
{"mark": "barY", "data": [{"species": "Adelie", "mass": 3750}, {"species": "Gentoo", "mass": 5000}], "x": "species", "y": "mass", "fill": "species"},
{"mark": "ruleY", "data": [0]}
]
}
```
The spec lists the marks and options; the playground checks a block as you type.
Notes
- Dark mode: the chart draws with
currentColor, so it follows your chat's text color. - Titles: a block's
"title"and"subtitle"become<p class="plotdown-title">and<p class="plotdown-subtitle">in the figure, styled inline asplotdown.jsdraws them; restyle them with those classes and!important. - Without React: a chat UI that writes HTML can use the script tag, which watches the page and renders a block once its JSON is complete.
Note
Tried with React 19.3.1, Streamdown 2.7.1 and Vite 8.3.8 on 2026-11-12, streaming a block in Microsoft Edge 156.