system.cssthe engine GitHub

The one mechanism you must understand: `--fg` → `currentColor`

The one mechanism you must understand: --fg → currentColor

system.css computes ink for every element (@layer core.color { :where(*) { … color: oklch(…) } }), and bridges SVG with :where(svg) { color: currentColor }. So the recipe is always:

  1. Set --fg (and --hue-shift when you want color) on an SVG element or a <g>.
  2. Paint with fill="currentColor" and/or stroke="currentColor".

That's it. The engine turns --fg into the actual color, and currentColor picks it up.

The --fg scale — n ∈ [-1 .. 0 .. 1]

--fg encodes contrast and chroma in one number. Sign picks the channel; magnitude is strength:

  • -1 → heavy neutral ink (grayscale, maximum contrast vs. the surface).
  • 0 → equals the background (invisible — useful for nothing, avoid).
  • +1 → maximum chromatic contrast (fully saturated ink at the current hue).

Negative is neutral/structural; positive is colored/expressive. So chart furniture (axes, gridlines, ticks, titles) is negative, and data marks (bars, bubbles, lines) are positive when you want them to carry hue. A practical ladder:

Layer --fg why
Faint gridlines -0.13 barely there, just enough to read values
Axis lines -0.4 structural but quiet
Labels (foLabel default) -0.8 at --type: -2 readable, clearly furniture
Data marks 1 full chroma — the data is the star (gf-69, per Mike)
Data marks (neutral) -0.85 a single-series chart that wants no hue
Quiet outlines (a silhouette behind the data) -0.25 the unselected states/counties, at low fill-opacity (gf-364)
A prior period -0.55 dashed last year's line under this year's (gf-347)
A secondary fill under the marks 0.2 the territory's states under its counties at 1 (gf-364)

(These are the SHIPPED values — the Metrics charts, the Charts shelf, the landing and the territory picker use exactly these rungs; don't invent intermediate steps.)

Hue per series: --hue-shift, never --hue-lock

THE LADDER (gf-182, per Mike — supersedes the full-wheel spread below): series step +30° apiece from the theme hue — an analogous run that stays cohesive with the page and re-themes as one family; the wheel spread read as a carnival against any theme. Fixed step, not divided by count (series four keeps its color when a fifth arrives). hueShift/hueStyle implement it; the shelf's "Categorical color" card shows the ladder re-theming live under a second surface hue.

The engine's hue is --hue + --hue-shift (unless --hue-lock overrides it). To give each series its own color, spread them around the wheel and set --hue-shift on the mark: shift = idx * 360 / n. Because it rides on the inherited --hue, the whole chart re-themes correctly when the page hue changes — admin-red vs. product-indigo just works.

Never reach for --hue-lock to color a series. --hue-lock hard-pins hue for an element and its entire subtree and refuses the cascade — it exists only for semantic primitives (success/info/danger pills) that must mean the same thing everywhere. Using it as a coloring tool poisons inheritance and is, per the color API's load-bearing rules, insurmountable tech debt. If you just want a color, that's --fg (+ --hue-shift).

The ONE sanctioned hue-lock use in a chart: a segment whose color IS a meaning — created = success, rejected = danger (view/statusbar.go's result bar). That is the semantic-primitive case the lock exists for, not series coloring.

Text via <foreignObject> — and its scaling trap

ALL chart text is engine-typed <foreignObject> — the foLabel helper in view/chartrender.go (2026-08-30, per Mike: no SVG <text>, no hand fonts, no px literals; the engine types everything). ⚠ The text lives in SVG coordinates: the whole drawing — text included — scales by rendered-width / viewBox-width, so --type: -2 only means what it says when the viewBox width ≈ the rendered width. Pin the builder's nominal W to its host container and say so in a comment (view/statusbar.go's "THE NOMINAL WIDTH IS THE DIALOG'S" warning is the exemplar).