AI-native
microcharts is built to be read and written by machines as well as people — a stream-friendly chart grammar, inline word-sized charts, a generated catalog, Markdown mirrors, and llms.txt so LLMs and agents get the API right the first time.
Most chart libraries are written for a human with a mouse. microcharts is written so a model can draw a chart
mid-sentence and a person can trust it. Two properties make that work: every chart renders from plain data, and
every chart carries a generated, human-readable summary — the same sentence a screen reader speaks, a crawler indexes,
and a model can quote back. Nothing is hidden in pixels, so a few tokens of text become a real, accessible instrument.
Above is the real thing, not a video: a scripted reply streams in, and the chart syntax it emits becomes shipped microcharts the instant each fence closes. Everything below is the contract behind it.
The AI reply is the newest home for a word-sized chart, not the only one — the same component lives in a product UI, a rendered report, and inline in docs like these. This page is the model-facing contract; Where they live shows the rest.
The grammar
A model emits a chart in one of two forms — both deliberately forgiving, the kind of thing a model produces reliably mid-stream. Pick a type and a form to see exactly what it writes, what it renders, and the React it's equivalent to:
```microchart sparkline 132 148 141 165 159 182 176 203 ```
<Sparkline
data={[132, 148, 141, 165, 159, 182, 176, 203]}
title="Revenue"
/>The body is whitespace- or comma-separated numbers for series charts, or key=value pairs for composites. These five
cover the questions an assistant answers most — trend, magnitude, change, attainment, cadence — but the same pattern
extends to any chart in the catalog: name a type, pick a body convention, map it to that chart's import.
Wire it up
Turning the grammar into components is a parser and a switch — no framework, a few lines.
Parse the stream
Pull fenced blocks and inline spans out of partially-streamed Markdown. A fence that hasn't closed yet stays raw, then morphs the moment it does.
const FENCE = /```microchart (\w+)\n([\s\S]*?)```/g;const INLINE = /`microchart (\w+) ([^`]+)`/g;const nums = (b: string) => b.split(/[\s,]+/).map(Number).filter(Number.isFinite);const kv = (b: string) => Object.fromEntries( b.split(/\s+/).flatMap((t) => { const i = t.indexOf("="); return i > 0 ? [[t.slice(0, i), t.slice(i + 1)]] : []; }), );Map type → component
One switch. Because geometry is pure and charts render from data alone, the model's chart is identical to one you'd
write by hand.
import { Sparkline } from "@microcharts/react/sparkline";import { SparkBar } from "@microcharts/react/sparkbar";import { Delta } from "@microcharts/react/delta";import { Bullet } from "@microcharts/react/bullet";import { ActivityGrid } from "@microcharts/react/activity-grid";export function ChartBlock({ type, body }: { type: string; body: string }) { switch (type) { case "sparkline": return <Sparkline data={nums(body)} title="Series" />; case "sparkbar": return <SparkBar data={nums(body)} title="Per period" />; case "delta": return <Delta value={Number(body.trim())} title="Change" />; case "bullet": { const p = kv(body); return ( <Bullet value={+p.value} target={p.target ? +p.target : undefined} bands={p.bands?.split(",").map(Number)} title="Attainment" /> ); } case "activity": return <ActivityGrid data={nums(body)} layout="strip" title="Cadence" />; default: return null; // unknown type → leave the text as-is }}Render inline vs block
An inline chart is decorative — the sentence describes it, so pass summary={false}. A standalone chart keeps its
summary; give it a title.
Wrap every inline SVG mark in className="mc-inline" — the class ships in @microcharts/react/styles.css (imported
once in the Quickstart). It seats the mark on the text baseline, so it rides the line like a word in any font. Leave
Delta bare; it is a text metric and owns its own baseline already.
NVDA closed +3.8%{" "}
<span className="mc-inline">
<TrendArrow value={0.038} summary={false} />
</span>{" "}
on the session — up <Delta value={0.038} summary={false} /> week over week.Prompt the model
A few lines teach the grammar. Reach for the technique that matches how the model is driven — structured output and tool calls first, since that's how most agents run today:
The default for anything production. Have the model return JSON and map it yourself — no parsing of prose. Constrain
type to an enum and the output is always renderable.
type ChartSpec = | { type: "sparkline" | "sparkbar" | "activity"; data: number[]; title?: string } | { type: "delta"; value: number; title?: string } | { type: "bullet"; value: number; target?: number; bands?: number[]; title?: string };// model returns ChartSpec (JSON mode / response_format) → render directlyfunction Chart(spec: ChartSpec) { switch (spec.type) { case "sparkline": return <Sparkline data={spec.data} title={spec.title} />; case "bullet": return <Bullet {...spec} />; // …one arm per type }}Expose a single tool the model calls when a chart helps. Its arguments are the chart spec, so there's nothing to parse and the schema enforces the shape.
const renderChart = { name: "render_chart", description: "Render a microchart when a number series helps the reader.", input_schema: { type: "object", required: ["type"], properties: { type: { enum: ["sparkline", "sparkbar", "delta", "bullet", "activity"] }, data: { type: "array", items: { type: "number" } }, value: { type: "number" }, target: { type: "number" }, bands: { type: "array", items: { type: "number" } }, title: { type: "string" }, }, },};// on tool_use → <Chart {...toolCall.input} />For free-form chat, drop this into the system message — enough for a capable model to emit valid blocks unprompted.
When a number series would help, emit a chart block instead of prose.Fenced for a standalone chart, inline `microchart …` for a word-sized one:```microchart sparkline132 148 141 165 159 182 176 203```Types: sparkline · sparkbar (signed = win/loss) · delta (one signed ratio)· bullet (value= target= bands=) · activity (intensities).Never invent a pie or gauge — they aren't supported.When you want a specific house style, show one exchange. Models mirror the shape of the example.
User: How did deploys trend this week?Assistant: Steady, with a strong Thursday:```microchart sparkbar6 9 5 11 7 12 8 10```User: And are we on track for the quota?Assistant: Close — `microchart delta +0.184` week over week puts us here:```microchart bulletvalue=72 target=80 bands=50,90```Never trust generated data blindly. Validate the type, coerce the numbers, and fall back to text when a block is malformed — the summary makes a graceful fallback trivial.
const KNOWN = new Set(["sparkline", "sparkbar", "delta", "bullet", "activity"]);function safeBlock(type: string, body: string) { if (!KNOWN.has(type)) return null; // unknown type → render raw text const data = nums(body); if (type !== "delta" && data.length === 0) return null; // no numbers → skip return { type, body }; // charts handle empty/NaN/±∞ already}Where it runs
A chart is plain text and JSON, so anything that reads or writes text can emit one and render it back — no SDK, no partnership, no island bridge. That spans the whole stack a model runs in today: the chat assistants that answer in prose, the coding agents and IDEs that scaffold the components from the typed catalog, and the frameworks and SDKs that wire tool-call output straight to charts. None of these is an integration we built — they work because the output is just text.
- Claude
- ChatGPT
- Gemini
- Grok
- DeepSeek
- Kimi
- Meta AI
- Mistral
- Perplexity
- Qwen
- Poe
- Cursor
- Claude Code
- Codex
- v0
- opencode
- Antigravity
- Windsurf
- Zed
- Cline
- Replit
- Continue
- Warp
- Amp
- Roo Code
- StackBlitz
- JetBrains AI
- Pi
- Copilot
- Vercel AI SDK
- OpenAI Agents
- LangChain
- Anthropic SDK
- Hugging Face
- Ollama
- Pydantic AI
- CrewAI
Integration scenarios
Every path from model output to a rendered chart, with the one thing each needs.
Machine-readable surfaces
Real, generated routes — the substance behind "AI-native", not a slogan. Point an agent here and it reads the true API instead of guessing.
The .md mirrors are real static files — /docs/ai has a sibling /docs/ai.md — so they resolve on any host, with no
runtime, rewrite, or middleware. This site also deploys behind a small Cloudflare Worker that runs on /docs/* only:
send Accept: text/markdown and the same doc URL returns the mirror inline, with Vary: Accept on both the Markdown
and the HTML response. That negotiation is a convenience layered on top of the mirrors, never a dependency — appending
.md always works.
/llms.txt points at one more surface the cards don't: /agent-setup.md, the paste-and-run setup
prompt for a coding agent, extracted from the Quickstart so the two can't drift. Or skip the URL
entirely:
The whole contract, on one card
Don't make a model memorize the catalog — All charts files every type by the question it answers, and the Charts index shows them at a glance. Everything an agent needs to emit correct microcharts fits on one card — copy it straight into a system prompt:
- →Import each chart from its own subpath —
@microcharts/react/<name>; add/interactiveonly for hover and keyboard. - →Static charts are hook-free — valid inside a React Server Component, zero client JavaScript.
- →Pass
dataand atitle; the generated summary handles accessibility. Usesummary={false}only when a chart is decorative inside describing text. - →Never add a runtime dependency, and never reach for a pie or gauge — they aren't shipped.
That's the entire surface area: a grammar a model already knows how to write, and charts a person can trust on sight.
Point an assistant at /llms.txt — or the fuller /llms-full.txt and the
machine-readable /catalog.json — or start from the Quickstart and render your
first chart in minutes.
Quickstart
Install @microcharts/react — the React sparkline and microchart library. Import styles once, render an accessible SVG chart in a Server Component or add /interactive for hover and keyboard.
All charts
The full microcharts catalog — every shipped chart at word size, grouped by the question its collection answers, each linking to a live page.