Skip to main content
Everything the chart draws — marks, grid lines, tick marks, the panel border, the graph background, axis and tick and data-label type — is painted from a stylesheet on the spec. You add one by piping a styles(...) part in, the same way you pipe config(...).
Because it lives on the spec, a stylesheet is serializable and travels with the chart — the same as mappings, scales and config.
Theme tokens (the built-in light/dark chrome, selected by colorScheme) are a separate surface covering the HTML chrome around the plot. See Theming.

The four keys

StyleRule[]
Applied where no mapped aesthetic decided the value — the look when nothing else speaks.
StyleRule[]
Applied over whatever a mapping decided.
Record<string, StyleTokenValue>
Named colours that entries reference with token('name').
Stylesheet[]
Composes other stylesheets underneath this one. Tokens merge name by name, lists concatenate, later wins. This is how a house style ships as a reusable preset.
Piping several styles(...) parts stacks them in order, each sitting above everything piped before it. Within a single list, order is specificity: the last matching entry that declares a property wins.

How a value is resolved

Each property resolves through three tiers, in order: The practical consequence: defaults never fight your mappings, and overrides always do. To recolour a series that is mapped to color, you need an overrides entry — a defaults entry loses to the scale. Entries scoped to a state ({ state: 'hovered' | 'dimmed' }) sit above the whole stateless cascade.

Targets

Call style.<target>(declarations, options?). Geom targets accept conditions; chrome and annotation targets are chart-scoped and take declarations only. Three things worth knowing up front:
  • A bar’s borderRadius is a token: 'none' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'full', defaulting to 'sm'. On dataLabel, panelBorder, style.graph and style.geom.tile it is a plain pixel number.
  • style.graph({ fontFamily }) is the chart’s base family: every text region without a family of its own follows it, in paint and in measurement.
  • style.graph({ textScale }) multiplies every text size at resolve time. 1 is the built-in; a preset can set a house scale through extends. gap, offset, padding and stroke widths do not scale.
  • style.graph({ padding }) is the outer frame, in pixels, and does not follow textScale. A number applies to every side; an object sets sides, and omitted edges keep the builtin 24.
  • Hide a panel-border edge with strokeWidth: 0.
  • A shape annotation’s border wears its color unless borderColor is declared; borderWidth: 0 draws none, and color: 'transparent' leaves a stroke-only outline. { annotation: id } addresses one annotation.
  • An arrow annotation’s borderWidth and shadow are its sticker look — an outline around the line and a drop shadow; both are off by default.
  • A difference arrow is two parts: the line (strokeWidth also sizes the route around the observations) and its label, style.annotation.differenceArrow.label. A colour no entry names falls to the series colour both ends share; the label’s box border takes the arrow’s colour unless borderColor is declared.
  • A text annotation’s background is its box; with none declared there is no box. The content’s own marks paint over the base type. An image’s borderRadius clips its corners, in pixels.
  • A pinned number and a comment are a marker dot plus the label bubble beside it (.label). A marker whose color no entry names borrows its observation’s colour; the swatch inside a pinned-number bubble always keeps it.
  • Stack totals (.aggregate) always sit outside the mark, so they take no .inside / .outside.
  • style.headlineItem is not callable — pick a part. .number.center is the donut-hole figure; without it the hole uses .number. gap on style.headline and style.legend is pixels and does not follow textScale. A bare style.legend entry is gap only; the overflow popover box is style.legend.popover (background, borderColor, borderWidth, borderRadius, paddingInline, paddingBlock, shadow).
  • Swatch fill opacity and bar corner radius follow the layer geom, including size-legend bubbles; style.legendItem.swatch and style.headlineItem.swatch only size the square box.

Colours

Any colour-valued property takes one of three forms:
{ light, dark } and token(...) resolve against the provider’s colorScheme. Prefer them to literals — a literal looks the same in both schemes.

Conditions

Geom entries take a where predicate (the same language as highlights), a state, and a layer:
layer scopes an entry to one authored layer id — the way to style a single series of a combo chart without touching the others.

Re-skinning through tokens

The engine’s built-in stylesheet sits behind every chart, and its defaults are written in terms of tokens. Redefining a built-in token name restyles the default it backs, with no entries at all:
The built-in defaults these produce: bars borderRadius: 'sm' with a 1px border; lines and areas strokeWidth: 2, lineType: 'solid'; areas alpha: 0.3; points size: 8; rules dashed; the y grid lines dashed; the panel border dashed with borderRadius: 6; the graph frame with borderRadius: 8; tick labels offset 10px; the dimmed state alpha: 0.4.

Presets with extends

extends composes stylesheets, which is how a house style becomes reusable:

Two token namespaces

textPrimary and textSecondary name a stylesheet token and a theme token. They are different values in different namespaces: the stylesheet tokens above drive the plot, while the theme tokens of the same name drive the chrome around it.

When an entry is invalid

An entry the engine can’t use is reported as an INVALID_STYLE_RULE warning and skipped — the chart still renders. Compile a spec headlessly to see warnings before wiring it into React.

Custom geoms

A custom geom renderer reads paint through the value accessors on its render input (getColor, getAlpha, getSize, …), which expose the data tier. To resolve the full cascade, build a resolver over the layer:
Supply colorScheme yourself — it defaults to 'light', and no exported hook carries the chart’s active scheme into a renderer.