Renderer tokens
@ponchia/ui/renderer resolves the live bronto theme for renderers that cannot
read CSS: Vega, xterm.js, a canvas or WebGL graph, a map, a 3D scene. Those
engines take literal colours, so a page that switches theme, skin, contrast or
the OLED surface has to resolve its tokens again before each repaint that
matters.
import { readTokens, observeTokens, vegaConfig, xtermTheme } from '@ponchia/ui/renderer';
const tokens = readTokens(); // { scheme, bg, panel, text, line, accent, categorical, … }
view = await embed(el, spec, { config: vegaConfig(tokens, { narrow: el.clientWidth < 480 }) });
terminal.options.theme = xtermTheme(tokens);
const stop = observeTokens((next) => {
terminal.options.theme = xtermTheme(next);
rebuildChart(vegaConfig(next));
});
The static exports serve a different host. charts.json
and tokens/vega.js are snapshots per light/dark theme for a
report, a file:// document or a build step. They cannot follow a skin or the
OLED preset; this module reads the page.
What readTokens() returns
Every colour is a renderer literal: #rrggbb when opaque, rgba(r, g, b, a)
when translucent. Canvas, SVG, d3-color, xterm.js, three.js and MapLibre all
accept both.
| Field | Token | Use |
|---|---|---|
scheme |
luminance of --bg |
'light' or 'dark' |
bg, bgElevated, panel, panelStrong |
--bg, --bg-elevated, --panel, --panel-strong |
Backgrounds; draw on panel |
text, textSoft, textDim |
--text, --text-soft, --text-dim |
Labels, secondary and tertiary ink |
line, lineStrong |
--line, --line-strong |
Grid and hairlines; axes and rules |
edge |
--edge |
A line that carries meaning: a relationship, connector or leader. 3:1 against the surface; line and lineStrong are decorative and are not |
accent, accentText, onAccent, focus |
accent family | The one emphasis, its text forms, focus |
selection |
--accent at 27% |
A translucent selection wash |
success, warning, danger, info |
status tier | Status only, never categories |
sans, mono |
--sans, --mono |
Font stacks for a canvas renderer |
categorical |
--cat-1..8 |
Eight hues in fixed order |
categoricalTint |
--cat-N-tint |
Each hue as a wash over panel |
categoricalInk |
--cat-N-ink |
Each hue as text, 4.5:1 on panel and tint |
sequential, diverging |
--chart-seq-*, --chart-div-* |
Ramps for magnitude and ± data |
readTokens(element) reads the custom properties as computed on element, so a
subtree that re-points tokens is honoured. Without css/dataviz.css on the
page, the categorical set and the ramps fall back to the packaged palette for
the resolved scheme, and the tint is computed over the live panel. Without a
DOM (SSR, tests) it returns the packaged values; pass { scheme: 'dark' } to
choose.
Following changes
observeTokens(callback, { element, signal }) calls back with fresh tokens
when the root's data-theme, data-bronto-skin, data-contrast,
data-surface, data-density, class or style changes and a token's value
moved with it, or when the system colour-scheme or contrast preference flips.
A host that writes its own inline properties on the root every frame (a canvas
zoom, say) costs one computed-style read per frame and no callback. Calls are
coalesced to one per animation frame. It returns a stop function; an
AbortSignal also stops it.
A host that already announces its own settled appearance change can read
readTokens() on that event instead and skip the observer.
Mappings
vegaConfig(tokens, { mode, narrow, background })— a Vega-Lite (default) or Vegaconfig. Quiet chrome in the bronto inks, no plot frame, the categorical palette asrange.category(a single series takes its first hue), the sequential ramp forordinal/ramp/heatmap, the diverging ramp fordiverging.narrowmoves the legend under the plot and thins ticks;backgrounddefaults to transparent so the host panel shows. A spec's ownconfigstill wins where Vega merges it. The statictokens/vega.jsis this mapping applied to each theme's packaged tokens.xtermTheme(tokens)— an xterm.jsITheme: page ink on the page background, the accent as cursor, the status colours for red, green, yellow and blue, and the categorical magenta and aqua inks for magenta and cyan, so all six ANSI hues differ.
For any other engine, map the fields yourself: they are already the roles a renderer needs.
Conversions
parseColor(value)— a resolved CSS colour to{ r, g, b, alpha }, clipped into sRGB: hex,rgb(),hsl(),oklch(),oklab(),lab(),lch()andcolor(srgb | srgb-linear | display-p3 | xyz-d65 | xyz-d50 …). It returns null forvar(),color-mix(),light-dark()and named colours, which only a browser can compute.formatColor(rgba)—#rrggbborrgba(r, g, b, a).resolveColor(value, { element })— any colour expression to a literal, asking the browser for whatparseColor()cannot compute.
The module is SSR-safe: it touches the DOM only inside a call that needs it. It owns no renderer and imports none.