Skip to content
microcharts

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.

assistant· business recap
streaming…

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:

the model emits
```microchart sparkline
132 148 141 165 159 182 176 203
```
renders as
Revenue203
the same thing in react
<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.

Chat assistants & models11
emit a chart block mid-reply
  • Claude
  • ChatGPT
  • Gemini
  • Grok
  • DeepSeek
  • Kimi
  • Meta AI
  • Mistral
  • Perplexity
  • Qwen
  • Poe
Coding agents & IDEs18
scaffold components from the typed catalog
  • Cursor
  • Claude Code
  • Codex
  • v0
  • opencode
  • Antigravity
  • Windsurf
  • Zed
  • Cline
  • Replit
  • Continue
  • Warp
  • Amp
  • Roo Code
  • StackBlitz
  • JetBrains AI
  • Pi
  • Copilot
Frameworks & SDKs08
map tool-call output to charts
  • 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:

agent cheat sheetcopy for a system prompt
grammar
microchart sparkline132 148 141 165 159 182 176 203 trend over timemicrochart sparkbar6 9 5 11 7 12 8 10 magnitude per period (signed values = win/loss)microchart delta+0.184 one signed change or ratiomicrochart bulletvalue=72 target=80 bands=50,90 a value against a target (value= target= bands=)microchart activity0 2 1 3 4 2 1 3 2 4 3 2 cadence / intensity over a period
rules
  • Import each chart from its own subpath — @microcharts/react/<name>; add /interactive only for hover and keyboard.
  • Static charts are hook-free — valid inside a React Server Component, zero client JavaScript.
  • Pass data and a title; the generated summary handles accessibility. Use summary={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.