accepted

Three-way theme split: UI theme (context), pitch theme (store), chart preset (prop)

Theming is split into three separate systems. **UI theme** (light/dark chrome) flows through React context. **Pitch theme** (pitch colours, line style) lives in a nanostore shared across the site. **Chart preset** (editorial/analytical/minimal encoding grammar) is a component prop. Each owns one concern; they never reach into each other.

architecturethemecore-contract

Context

Three entangled problems wear “theme” as their label:

  1. UI chrome — page background, text colour, tooltip surface, border treatment. Changes with user light/dark preference.
  2. Pitch appearance — grass colour, line colour, line weight, crest styling. A cross-chart concern — every pitch in a dashboard should look the same. Changes per-site or per-demo, not per-user.
  3. Chart encoding grammar — which palette ramps, which default shape/size defaults, which emphasis colours. Varies per component and per editorial preset (Opta editorial vs StatsBomb analytical).

A single unified theme solves none of these cleanly. User light-dark shouldn’t repaint the pitch. Pitch-colour changes shouldn’t flip the tooltip palette. Per-chart preset changes shouldn’t cascade to chrome.

Decision

Three parallel systems, never reaching across:

  • UI theme — React context (ThemeContext). Components consume it for chrome. Default-published outputs look right in light theme; dark is opt-in.
  • Pitch theme — nanostore, pitch-scoped. Demos and dashboards subscribe in one place; every pitch component reads from the same store. No prop plumbing.
  • Chart preset — a preset prop on each chart component. Encoding grammar. Not stored globally because presets are editorial choices, not user preferences.

Consequences

  • Swapping user light/dark never affects pitch appearance or chart encoding.
  • The pitch store is a site concern, not a library concern — other apps using Campos can wire their own store or pass colours as props; the library does not publish a pitch-theme singleton.
  • Data-encoding palettes live in compute, not in any theme system (see style-injection-callback-first).
  • New concerns that sound like “theme” must answer “which of the three?” before they get added; the split is the design constraint that prevents re-entanglement.
← All decisions