# AI-native (/docs/ai)

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](/docs#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 grammar has two forms — a fenced `microchart` block for a standalone chart, or an inline `microchart <type> <data>` span inside a sentence. Body is whitespace/comma numbers, or key=value for composites.

**Sparkline** — trend over time

```microchart sparkline
132 148 141 165 159 182 176 203
```

Equivalent React:

```tsx
<Sparkline
  data={[132, 148, 141, 165, 159, 182, 176, 203]}
  title="Revenue"
/>
```

**SparkBar** — magnitude per period (signed values = win/loss)

```microchart sparkbar
6 9 5 11 7 12 8 10
```

Equivalent React:

```tsx
<SparkBar
  data={[6, 9, 5, 11, 7, 12, 8, 10]}
  title="Deploys per day"
/>
```

**Delta** — one signed change or ratio

```microchart delta
+0.184
```

Equivalent React:

```tsx
<Delta value={0.184} title="Week over week" />
```

**Bullet** — a value against a target (value= target= bands=)

```microchart bullet
value=72 target=80 bands=50,90
```

Equivalent React:

```tsx
<Bullet
  value={72}
  target={80}
  bands={[50, 90]}
  title="Quota attainment"
/>
```

**ActivityGrid** — cadence / intensity over a period

```microchart activity
0 2 1 3 4 2 1 3 2 4 3 2
```

Equivalent React:

```tsx
<ActivityGrid
  data={[0, 2, 1, 3, 4, 2, 1, 3, 2, 4, 3, 2]}
  layout="strip"
  title="Commit activity"
/>
```

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](/docs/charts): 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.

```ts
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.

```tsx
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.

```tsx
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:

#### Structured JSON

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.

```tsx
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 directly
function 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
  }
}
```

#### Tool use

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.

```ts
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} />
```

#### System prompt

For free-form chat, drop this into the system message — enough for a capable model to emit valid blocks unprompted.

```text
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 sparkline
132 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.
```

#### Few-shot

When you want a specific house style, show one exchange. Models mirror the shape of the example.

```text
User: How did deploys trend this week?
Assistant: Steady, with a strong Thursday:

```microchart sparkbar
6 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 bullet
value=72 target=80 bands=50,90
```
```

#### Guardrails

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.

```ts
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 & models** (emit a chart block mid-reply): Claude, ChatGPT, Gemini, Grok, DeepSeek, Kimi, Meta AI, Mistral, Perplexity, Qwen, Poe
- **Coding agents & IDEs** (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 & SDKs** (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.

#### Streaming chat reply — morph blocks as they close

Parse the partial Markdown on every token; render a fence as raw text until it closes, then swap in the chart.
Word-sized charts land inside the sentence, standalone ones below it. The live demo at the top of this page is exactly
this shape.

```tsx
// on each streamed chunk:
const nodes = parse(accumulatedText); // text | { type, body, closed }

return nodes.map((n) =>
  n.type === "text" ? <Inline text={n.v} />
  : n.closed        ? <ChartBlock type={n.type} body={n.body} />
  :                   <pre>{n.raw}</pre>, // still streaming → show raw
);
```

#### React Server Components — zero client JavaScript

Static entries are hook-free, so an RSC assistant renders the model's charts to HTML on the server. Nothing hydrates;
the chart ships as markup. Add `/interactive` only where the reader needs to interrogate a value — hover, keyboard,
touch, or click-to-pin — and read it back with `onActive` / `onSelect`. Every chart but WindBarb has an interactive
twin; `catalog.json` names it per entry, so an agent never has to guess.

```tsx
import { Sparkline } from "@microcharts/react/sparkline"; // no "use client"

export default async function Answer({ spec }: { spec: ChartSpec }) {
  return <ChartBlock type={spec.type} body={spec.body} />; // pure SVG, server-rendered
}
```

#### Tool / function-call output — rows straight to charts

When a tool returns rows, render them instead of asking the model to describe them in prose — a `Sparkline` per row, a
`Bullet` for a KPI against target.

```tsx
{rows.map((r) => (
  <tr key={r.id}>
    <td>{r.name}</td>
    <td><Sparkline data={r.history} summary={false} /></td>
    <td><Bullet value={r.value} target={r.goal} summary={false} /></td>
  </tr>
))}
```

#### Programmatic & React-free — outside a component tree

Need a chart in a README, an email, or a non-React runtime? Render the static component to an SVG string on the server.
Static entries are hook-free and listener-free, so the markup they emit stands on its own.

```ts
import { renderToStaticMarkup } from "react-dom/server";
import { Sparkline } from "@microcharts/react/sparkline";

const svg = renderToStaticMarkup(<Sparkline data={data} title="Revenue" />);
// → a self-contained <svg> string for anywhere HTML goes
```

#### Validation & safety — malformed data never breaks a render

Every chart already handles the degenerate cases — empty, single value, all-equal, nulls, NaN, ±Infinity — with an
honest short summary instead of a broken chart. Layer the type/coercion guardrails above and a bad block downgrades to
plain text, never a crash.

## 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.

- [/llms.txt](/llms.txt): A hand-curated index of the docs for LLM tools, with explicit “does not support” notes to head off hallucinations.
- [/llms-full.txt](/llms-full.txt): Every doc page concatenated into one text file — drop the whole API into a context window at once.
- [/catalog.json](/catalog.json): Machine catalog: each chart’s name, import paths, data shape, and props (plus a shared-grammar block) — generated from the same registry that builds this site, and validated in CI against its JSON Schema.
- [*.md mirrors](/docs/ai.md): Append .md to any docs page for a Markdown copy an agent reads without parsing HTML — every component expanded to text and code.

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`](/agent-setup.md), the paste-and-run setup
prompt for a coding agent, extracted from the [Quickstart](/docs/quickstart) so the two can't drift. Or skip the URL
entirely:

<CopyAgentSetup />

## The whole contract, on one card

Don't make a model memorize the catalog — [All charts](/docs/charts) files every type by the question it answers, and
the [Charts index](/charts) 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 sheet.** Grammar:

- `microchart sparkline` — 132 148 141 165 159 182 176 203 (trend over time)
- `microchart sparkbar` — 6 9 5 11 7 12 8 10 (magnitude per period (signed values = win/loss))
- `microchart delta` — +0.184 (one signed change or ratio)
- `microchart bullet` — value=72 target=80 bands=50,90 (a value against a target (value= target= bands=))
- `microchart activity` — 0 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.

Surfaces: /llms.txt · /llms-full.txt · /catalog.json · *.md mirrors

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`](/llms.txt) — or the fuller [`/llms-full.txt`](/llms-full.txt) and the
machine-readable [`/catalog.json`](/catalog.json) — or start from the [Quickstart](/docs/quickstart) and render your
first chart in minutes.
