We had six charts built by four people and they all looked slightly different: different grays for gridlines, different fonts, three shades of the same 'brand blue'. I wanted the model to generate a single theme object and make every chart consume it.
The trick was to define the palette as semantic roles (grid, axis, series-1..n, positive, negative, muted) rather than raw hex, and to forbid any literal color inside chart components. It also had to derive a sequential and a diverging scale from the brand hue so heatmaps stay on-brand.
Sharing the token spec, would love critiques on the diverging-scale approach.
Produce a single chart theme module (TypeScript) that every visualization in an app imports. Structure:
1. A `tokens` object with SEMANTIC roles only: background, gridline, axisLabel, axisLine, seriesCategorical (an ordered array of 8, colorblind-safe, checked against deuteranopia), positive, negative, neutral, annotation.
2. Derived scales from a single {brandHue} input: a sequential scale (light->brand, 7 stops) and a diverging scale (negative hue <- neutral -> positive hue, 9 stops) generated in OKLCH so lightness steps are perceptually even, not naive HSL.
3. Type tokens: font stack, and a modular type scale for title/label/tick.
Hard rules: chart components must reference tokens by role; a literal hex or px font-size inside any chart is a lint error you should call out. Provide a `withTheme(spec)` helper that injects these into a Vega-Lite config. Include a contrast check asserting axisLabel-on-background >= 4.5:1 and print the computed ratio. Do not use pure black (#000) or pure white (#fff) for text or background.