Troubleshooting
Fixes for the common microcharts failures: unstyled charts, a chart invisible on a dark panel, a 'use client' error, the removed onPointFocus callback, an inert animate prop, a chart that won't fill its container, and serif labels.
Most issues trace back to one of a handful of causes. Each fix is below, with a link to the guide page that explains it in full.
Charts render with no color, no strokes, just plain shapes
The stylesheet isn't loaded. Every visual property comes from styles.css: color, stroke weight, label size, the whole
theme. Import it once:
import "@microcharts/react/styles.css";Without it a chart is still valid, accessible SVG: the markup and the accessible name are both there, with no styling applied. See Quickstart for where the import goes in your framework.
A chart is invisible (or nearly) on a colored or dark panel
Charts never paint their own background; they render transparent SVG. On a plain light or dark page you never notice,
because the built-in light and dark tokens track prefers-color-scheme. It breaks when a chart sits on a surface whose
color doesn't match the page's color scheme: a dark-branded card on an otherwise light page, or a colored KPI tile.
The tokens don't know that scope looks dark; they only know what the OS or browser reports.
Fix it by rebinding the token scope to match the surface, not the page:
.dark-panel {
--mc-stroke: #f5f5f5; /* text + data strokes; add --mc-neutral for muted labels */
}or force the whole preset for that scope:
<div data-mc-theme="dark">
<Sparkline data={data} />
</div>See Theming for the full token set and how scope, preset, and prop precedence works.
Error: ... 'use client' ... when importing a chart
You imported a chart's /interactive entry into a Server Component:
// ❌ interactive entries are client components — this throws in an RSC
import { Sparkline } from "@microcharts/react/sparkline/interactive";Two ways out. Render the static default entry if you don't need hover, keyboard, touch, or selection:
import { Sparkline } from "@microcharts/react/sparkline";Or, if you do need interactivity, move the import into a component marked "use client" and render that component from
your server tree. See Quickstart for the static-vs-interactive split.
onPointFocus / onRunFocus no longer fire
They were removed when every interactive chart moved onto one shared contract. The replacement is onActive, which
fires for the same reason on every chart in the catalog:
// ❌ removed
<Sparkline data={data} onPointFocus={(i) => setHovered(i)} />
// ✅ one name, everywhere
<Sparkline data={data} onActive={(d) => setHovered(d?.index ?? null)} />The payload is a MicroDatum, { index, value, label?, formatted? }, where value is the raw number and formatted
is the chart's ready-to-display string; it's null when the chart clears. onPointFocus on Sparkline and SparkBar and
StreakSpark's onRunFocus map across directly. index identifies the chart's navigable unit, which isn't always a
series index: on Waveform it's a bucket, on Hypnogram a run, on SegmentedBar a segment. Each chart's page names its own
unit. For click-to-pin, add onSelect; see Accessibility.
animate does nothing
The entrance engine isn't loaded. animate is a flag; the code that runs the animation ships in a separate side-effect
import you add once, anywhere in your client bundle:
import "@microcharts/react/motion";In development the chart logs this to the console when the engine is missing. Three other reasons animate is inert,
none of them bugs: the reader has prefers-reduced-motion: reduce, the chart is a static entry (animate is an
/interactive-only prop), or the markup came from the server and is being hydrated rather than freshly mounted, where
an entrance would animate content the reader can already see. The entrance runs once per mount by design; remount with a
changed key to replay it. The full contract is on Motion.
A selected unit stays lit after I click away
It shouldn't, from 0.16. A pin is dropped by a pointer press anywhere outside the chart, by Escape from anywhere on
the page, and by selecting the same unit again; each fires onSelect(null). Blur is not one of them, so moving focus to
a panel that reads the pinned value keeps it.
If a pin is still stuck, the chart is controlled: selectedIndex makes your state the only thing that can move the
selection, and the chart just reports the dismissal. Clear it in your handler, or switch to defaultSelectedIndex to
seed the selection once and let the chart own it after that.
const [picked, setPicked] = useState<number | null>(null);
<SparkBar data={data} selectedIndex={picked} onSelect={(d) => setPicked(d?.index ?? null)} />;The chart won't stretch to fill its container
Give it a width and make sure the parent is block-level (a flex or grid item, or a div). A chart can't grow past an
inline parent no matter what you set on the chart itself.
<Sparkline data={data} className="w-full" />.mc-root, the chart's own <svg>, is display: inline-block by default, so it flows next to text; add
display: block when you want it on its own line.
A width alone is enough, and it is enough on a className. Before 0.15 it wasn't: the chart set both width and
height as SVG attributes, your CSS replaced only the width, and preserveAspectRatio then fitted the drawing to the
axis you hadn't touched. The element stretched, the drawing didn't, and you got a small mark centered in a wide box —
which reads as a chart that failed to load. If you carry a h-auto next to a w-full for that reason, you can drop it.
Interactive entries behave identically: style, className, and width/height size an interactive chart exactly the
way they size its static twin, and hit-testing stays exact at any size, so the crosshair tracks the cursor and a touch
drag scrubs the unit under the finger. Full recipes, including the inline-in-a-sentence case, are on
Sizing & scaling.
A chart stacks on its own line instead of sitting inline with text
This is usually a CSS reset, not a microcharts bug. .mc-root's display: inline-block is declared inside a
zero-specificity :where() so your styles always win, including ones you didn't mean to apply. Tailwind's Preflight
sets a plain svg { display: block; ... } rule, which has higher specificity than any :where() selector and
overrides it, dropping the chart onto its own block-level line. Re-assert the layout you want at the call site:
<Sparkline data={data} className="inline-block" />or wrap it in className="mc-inline" for text-flow placement — see
Sizing & scaling.
Chart labels look wrong — a serif fallback, or mismatched with the page
Charts inherit font-family from the page; they don't set one of their own. If the surrounding page never declares a
font (a bare sandbox, a minimal starter), SVG text falls back to the browser's default serif. See
Theming → The font for why it inherits and how to set it per-scope.
Performance
Where the size and speed numbers come from: 106 chart types at ~2–7 kB interactive · ~1–4 kB static, a static server render of about 0.03 ms each, and the commands that regenerate every figure.
Design notes
Why microcharts defaults the way it does — no axes or legends, static-first entries, one shared grammar, generated summaries, size budgets as CI gates, and the bar a new chart type has to clear.