Configure a chart once. Ship it as your own code.
Graphite is a chart library for Next.js and React. You design the look in the app — colors, fonts, shape, light/dark, surface — define the data shape your component accepts, and download a small set of TypeScript files that live in your project. There is no runtime dependency on this app.
From zero to a chart in five minutes
unzip graphite-line-chart.zip -d ./
import { LineChart } from "@/components/charts/LineChart";
<LineChart data={rows} />Three steps, top to bottom
Tips: hover any chart for a tooltip, use ← → to step through points and Esc to clear. The sun/moon button switches the app; the chart's own mode is set under Design → Chart mode.
Add the downloaded files to your project
The zip mirrors a standard App Router layout. Unzip it at the root of your Next.js project; existing folders are merged, nothing else is touched.
your-app/ ├─ app/ │ ├─ globals.css ← Graphite tokens (append to your existing file) │ └─ dashboard/page.tsx ← example usage (optional) ├─ components/charts/ │ ├─ LineChart.tsx ← the component │ ├─ LineChart.types.ts ← schema + props contract │ ├─ LiveLineChart.tsx ← SWR polling example (optional) │ └─ graphite/Chart.tsx ← the SVG renderer (React only) └─ lib/ └─ graphite.theme.ts ← your design choices as a typed theme
- 1Unzip into your project root. Merge globals.css into your existing app/globals.css if you already have one.
- 2Make sure zod is available (used to validate rows) — and swr only if you keep the Live client example.
- 3Set data-theme="light" or "dark" on <html>, or leave it off to follow the user's system preference.
npm install zod # required npm install swr # only for LiveLineChart.tsx
// app/layout.tsx — light/dark is driven by a data attribute on <html>
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" data-theme="dark" suppressHydrationWarning>
<body>{children}</body>
</html>
);
}Generate charts from Claude Code
Graphite includes a Model Context Protocol server, so Claude Code (or any MCP client) can generate charts straight into the project you are working on. It runs inside the same app — including the Docker container — at /mcp over Streamable HTTP. It is open: no API key, no login.
http://localhost:6786/mcp1. Start Graphite
docker compose up -d --build # serves the app and the MCP server on :6786
Or run npm run dev for development (the endpoint is then http://localhost:3000/mcp). Opening the endpoint in a browser shows a short JSON description, which is a quick way to check it is reachable.
2. Add it to Claude Code
claude mcp add --transport http --scope user graphite http://localhost:6786/mcp
claude mcp add --transport http --scope project graphite http://localhost:6786/mcp
The project scope writes a .mcp.json at the repo root that you can commit. You can also create it by hand:
{
"mcpServers": {
"graphite": {
"type": "http",
"url": "http://localhost:6786/mcp"
}
}
}3. Check the connection
claude mcp list # graphite: http://localhost:6786/mcp (HTTP) - ✔ Connected
A project-scoped server shows Pending approval until you start claude in that repo and approve it. Inside a session, /mcp lists the server and its tools. Claude asks the first time it calls a Graphite tool; allow it for the session or always. If it shows as failed, open http://localhost:6786/mcp in a browser — you should see a short JSON description.
Tools
list_chart_typesThe 12 chart types, the row shape each expects, default field names and an example row.get_design_optionsPalettes (with colors), fonts, surfaces, curves, modes, sample scenarios and value ranges.validate_dataChecks real rows against a chart type, infers the x and value fields, flags missing fields or wrong types and returns the zod schema.generate_chartReturns the component, types, theme, CSS tokens and renderer as files to write — plus a live preview link and a link that opens the chart in the studio.generate_themeOnly lib/graphite.theme.ts and the CSS tokens — restyle every chart in a project at once.get_chart_runtimeThe shared SVG renderer, components/charts/graphite/Chart.tsx.generate_chart accepts the same choices as the studio: type, data (example rows), x, keys, title, unit, componentName, palette (an id or four hex colors), font, radius, stroke, fill, curve, grid, legend, labels, mode, surface — and files to choose which files come back. By default you get the component, types, theme, CSS tokens and renderer; add usage and live for the example page and SWR client. Leave out runtime and theme once a project already has them.
Things to ask
Other clients and remote hosts
- Cursor, Windsurf, VS Code and other HTTP-capable clients: use the same
{ "type": "http", "url": "http://localhost:6786/mcp" }entry in their MCP config. - Clients that only speak stdio (for example Claude Desktop's config file): bridge with
npx -y mcp-remote http://localhost:6786/mcpas the command. - Graphite on another machine: use that host's address instead of
localhost. If it sits behind a reverse proxy or a different public URL, setPUBLIC_URL(for examplehttps://charts.example.com) indocker-compose.ymlso preview and studio links point to the right place. - No authentication: anyone who can reach the port can call the tools. They only generate code and never read or write files on the server, but keep the port on localhost or a trusted network.
The component never fetches
Every chart takes a data prop: an array of row objects whose field names you chose in the Data step. Where the rows come from is up to you.
async function Page() {
const res = await fetch(API, {
next: { revalidate: 60 },
});
const rows = LineDatumSchema
.array()
.parse(await res.json());
return <LineChart data={rows} />;
}"use client";
function Live() {
const { data, isLoading } =
useSWR<LineDatum[]>(url, fetcher);
return (
<LineChart
data={data ?? []}
loading={isLoading}
/>
);
}Rows are validated at the boundary with the generated zod schema, so a shape mismatch fails loudly in development instead of rendering an empty chart.
What each chart expects per row
Field names are yours to choose in the Data step; the shapes below are the defaults the app starts from.
label · series…{ "label": "08:00", "eu_west_1": 140, "us_east_1": 111 }label · series…{ "label": "Mon", "grid": 24, "solar": 18 }label · value{ "label": "Edge", "value": 312 }label · series…{ "label": "Q1", "paid": 20, "social": 15, "organic": 10 }name · value{ "name": "Compute", "value": 38 }x · value{ "x": 212, "value": 64 }axis · series…{ "axis": "Latency", "now": 82, "prev": 65 }day · hours[]{ "day": "Mon", "hours": [3, 5, 8, …24 numbers] }label · values[]{ "label": "BTC", "values": [41.2, 43.8, 42.1, …] }name · value{ "name": "Requests", "value": 12400 }name · value (0–100){ "name": "CPU load", "value": 68 }name · value{ "name": "Storage", "value": 3570 }Every generated component accepts
datareadonly Datum[]Rows to render. Field names match what you defined in the Data step.loadingbooleanRenders a skeleton in place of the chart.emptyStateReactNodeShown when data is empty and not loading.onPointClick(d: Datum) => voidFires with the row under the cursor (click, or Enter while stepping with the keyboard).formatValue(v: number) => stringCustom number formatting for axes, labels and tooltips.mode / surface"auto" | … / "solid" | …Override the theme defaults per instance.classNamestringMerged onto the root element.Light, dark and glass
graphite.theme.ts holds your palette, font, radius, stroke, fill and both mode token sets. Import it once and pass it to every chart; change it in one place to restyle them all.
mode="auto"surface="glass"surface="transparent"<LineChart data={rows} mode="auto" surface="glass" />
// mode: "light" | "dark" | "auto" — auto follows <html data-theme> then prefers-color-scheme
// surface: "solid" | "glass" | "transparent"