styles(...) part in, the same way you pipe 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 colors that entries reference with
token('name').Stylesheet[]
Composes other stylesheets underneath this one. Tokens merge name by name,
lists concatenate, later entries decide. This is how a house style ships as a
reusable preset.
styles(...) parts stacks them in order, each above everything piped before it.
Within a single list, order is specificity: the last matching entry that declares a property decides.
How a value is resolved
Each property resolves through three tiers, in order:
So
defaults never fight your mappings, and overrides always do. To recolour a group 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
Callstyle.<target>(declarations, options?). Geom targets take conditions (where, state,
layer). Annotation targets take { annotation: id }. Chrome targets take declarations only. Every
entry may also carry a coord.
Notes on the vocabulary:
-
A bar’s
cornerRadiusis a token,'none' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'full', defaulting to'sm', or a number of pixels:style.geom.bar({ cornerRadius: 3 }). A box rounds withcornerRadiusin pixels, one number or one per corner:dataLabel, the tooltip, a legend pill, a text or image annotation and a callout label takestyle.tooltip({ cornerRadius: { topLeft: 0, topRight: 8, bottomRight: 8, bottomLeft: 8 } });panelBorder,style.graphand the edit outline take one number. -
A box is painted with
fill, and its border withstrokeandstrokeWidth: the graph frame, the tooltip, a data label, a legend pill, a text annotation and a callout label all take the same words. -
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.fontSize,fontWeight,lineHeightandtextColorongraphare the same type words; the content targets declare their own. -
style.heading,style.heading.h1andstyle.heading.h2are the title and the subtitle. A bare heading entry addresses both levels. Color, family and size on rich-text runs paint over this base type.style.captionandstyle.sourceare the footer type;style.source.linkis the URL. -
style.graph({ textScale })multiplies every text size at resolve time.1is the built-in; a preset can set a house scale throughextends.gap,offset,margin, padding and stroke widths do not scale. -
style.graph({ padding })is the outer frame, in pixels, and does not followtextScale. A number applies to every side; an object sets sides, and omitted edges keep the builtin 24. -
marginis air around a chrome target that occupies a layout slot (header,footer,legend,headline,axisLabel,tickLabel). A number sets every side; omitted object sides stay absent, so they do not overwrite earlier entries.0closes a side.gapis the space between items inside a chrome target. See Layout. The built-in tickoffsetis 10, left and right 14. -
Every geom takes a
shadow('none'or{ offsetX, offsetY, blur, color }) and ablendMode('normal' | 'multiply' | 'screen' | 'overlay' | 'darken' | 'lighten'); so doesstyle.graphfor the frame. A line, an area and the polar kinds read both once per layer, from the first observation. The built-inhoveredstate lifts the geom under the pointer with a soft shadow, bars with a larger one.style.geom.point({ shadow: 'none' }, { state: 'hovered' })drops it. -
Every text target takes the same type words:
fontFamily,fontSize,fontWeight,fontStyle('normal' | 'italic' | 'oblique'),letterSpacing(pixels, negative allowed),textTransform('none' | 'uppercase' | 'lowercase' | 'capitalize'),textDecoration('none' | 'underline' | 'line-through'),textOutlineColorandtextOutlineWidth(the width visible outside the glyphs),textShadow(the geomshadowvalue),lineHeightandtextColor. Layout measures the string with its case and letter spacing applied, so a label reserves the space its glyphs take. Tick labels carry a built-in outline in the graph background, so a label stays legible where it crosses a grid line;style.tickLabel({ textOutlineWidth: 0 })removes it. -
A line, an area and a rule take
lineCap('butt' | 'round' | 'square') andlineJoin('miter' | 'round' | 'bevel'). None has a built-in: a path keeps its rounded caps until an entry says otherwise. -
Every geom takes
blur(pixels) andbrightness,contrastandsaturation(multiples,1unchanged). A filter declared without a state draws on the geom itself. A filter declared with{ state: 'dimmed' }draws on the whole layer over the geoms already painted:style.geom({ blur: 1, saturation: 0.3 }, { state: 'dimmed' })blurs and grays a dimmed layer. -
fillis a paint, not only a color. Beside a color, a token or a light-dark pair it takes:- a gradient:
{ gradient: 'linear', angle?, stops: [{ offset, color }, …] }or{ gradient: 'radial', stops }, with at least two stops; - a pattern:
{ pattern: 'diagonal' | 'dots' | 'crosshatch' | 'lines', color, background?, size? }, repeating everysizescreen pixels.linesdraws one level line across the top third of each tile; - an image:
{ image, fit?, alpha?, size?, fallback }, a data URI only.fit: 'tile', the default, repeats it everysizescreen pixels wide at its own proportions;fit: 'stretch'fills the shape with one copy.fallbackalways paints behind the image;alphaaffects only the image.
strokestays a color. Bars, tiles, polar bars, polar areas, the graph’s frame and HTML boxes draw the paint. The swatches of bars, tiles and polar bars draw it too when it covers the swatch’s whole color group: declared for the layer, or by awherethat reads only the color variable. Awhereon any other variable picks out observations, so it stays off the swatch. Everywhere else only one color can be drawn (a contrast pick, a hover marker, a line’s wash, a cartesian area, a point, any other swatch), so it is picked from the paint: a gradient’s first stop, a pattern’s color, an image’s fallback. - a gradient:
-
Every geom and
style.graphtake anoverlay: a paint drawn over what the target already drew. It takes whatfilltakes, a list of those with the first on top, or'none':Usefallback: 'transparent'to keep the chart’s colors visible beneath an image overlay. An opaque fallback covers them even when the image’salphais low. A geom’s overlay is drawn over everything its layer painted and nowhere else, a geom from a package included. It is as strong as the paint under it: over a bar whosefillAlphais0.3it is drawn at0.3too. It is drawn for the layer as one. Awhereentry is decided by the layer’s first observation, which the compiler warns about, and{ state: 'hovered' }sets the overlay of the shape under the pointer. A marker drawn on the layer for a hover elsewhere keeps the layer’s resting overlay. The graph’s overlay does not reach the tooltip, which is a box of its own. A geom’s overlay is painted through a mask of the layer, so the browser draws that layer twice. On a layer with many thousands of shapes, the graph overlay costs less than a geom overlay. Geom overlays support SVG renderers only. A portal-rendered geom ignores a non-empty overlay and reportsINVALID_STYLE_RULE. Anoverlaydeclared with{ state: 'dimmed' }also reportsINVALID_STYLE_RULEand is ignored; other properties in the entry still apply, and the resting overlay fades with the layer. -
Every box takes a
shadowand analphabeside itsfill: a data label, the tooltip, a legend pill and the legend popover, a text or image annotation, a callout or difference-arrow label, andstyle.graphfor the frame.style.tooltip({ shadow: { offsetX: 0, offsetY: 4, blur: 12, color: 'rgba(0, 0, 0, 0.2)' }, alpha: 0.95 })lifts and fades the popover; the frame’salphathins its background alone, so the plot keeps its opacity. -
A point takes a
symbol('circle' | 'square' | 'diamond' | 'triangle' | 'cross' | 'star' | 'wye'), built incircle. Every symbol covers the area of the circle itssizenames, sostyle.geom.point({ symbol: 'diamond' })keeps the layer’s weight; the hover marker, the highlight dot and the swatch draw the same symbol. -
Hide a panel-border edge with
strokeWidth: 0. The built-ingridLine.xandtickLineentries use the samestrokeWidth: 0, so vertical grid lines and tick marks appear only once you give them a width (and tick marks alength). -
A shape annotation’s border wears its
fillunlessstrokeis declared;strokeWidth: 0draws none, andfill: 'transparent'leaves a stroke-only outline.{ annotation: id }addresses one annotation. -
An arrow annotation’s sticker look is its own part,
style.annotation.arrow.outline({ stroke, strokeWidth, shadow }): an outline around the line and a drop shadow, both off by default. -
A difference arrow is two parts: the line (
strokeWidthalso sizes the route around the observations) and its label,style.annotation.differenceArrow.label. A color no entry names falls to the group color both ends share; the label’s box border takes the arrow’s color unlessstrokeis declared. -
A text annotation’s
fillis its box; with none declared there is no box. The content’s own marks paint over the base type. An image’scornerRadiusclips its corners, in pixels. -
A pinned number and a comment are a marker dot plus the label bubble beside it (
.label). A marker whosefillno entry names borrows its observation’s color; the swatch inside a pinned-number bubble always keeps it. -
Stack totals (
.aggregate) always sit outside the geom, so they take no.inside/.outside. -
style.headlineItem({ gap })is the space between a card’s rows; everything else on a card is a part..number.centeris the donut-hole figure; without it the hole uses.number.gaponstyle.headline,style.headlineItem,style.legendandstyle.legendItemis pixels and does not followtextScale. A barestyle.legendentry takesgap,marginandfocusStroke; the overflow popover box isstyle.legend.popover(fill,stroke,strokeWidth,cornerRadius,paddingInline,paddingBlock,shadow,alpha). -
Swatch opacity and bar corner radius follow the layer geom, including size-legend bubbles;
style.legendItem.swatchandstyle.headlineItem.swatchonly size the square box.
Colors
Any color-valued property takes one of three forms:{ light, dark } and token(...) resolve against the provider’s
colorScheme. Prefer them to literals, which look the same in both
schemes.
Conditions
Geom entries take awhere predicate (the same language as highlights),
a state, and a layer:
layer scopes an entry to one authored layer id, which is how you style a single layer of a
combo chart without touching the others.
Every entry, chrome included, takes a coord, which keeps it to graphs drawn in that coordinate
system. This stylesheet rules off the left edge of a bar graph and leaves a pie without one:
coord is 'cartesian' or 'polar'; a flipped graph is cartesian.
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
cornerRadius: 'sm' with a 1px border; lines and areas
strokeWidth: 2, dashArray: []; areas fillAlpha: 0.3; points size: 8 with a 1px outline;
tiles cornerRadius: 8 and no border; rules dashed at 1px; the y grid lines dashed, the x grid
lines and tick lines strokeWidth: 0; the panel border dashed with cornerRadius: 6; the graph
frame with cornerRadius: 8 and padding: 24; 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 both a stylesheet token and a theme token. They are different
values in different namespaces: the stylesheet tokens above drive the plot, the theme tokens of the
same name drive the chrome around it.
When an entry is invalid
An entry the engine cannot use is reported as anINVALID_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, read styleReaders on the render input (or useGeomStyleReader(layer)):
kit.style.geom.lollipop.stem({ stroke: token('brand') }). See what the geom paints.
