system
400.wtf

400.wtf / system / docs · docs/ADR/0011-surfaces-instead-of-themes.md

ADR-0011 — Surfaces instead of themes

Status: Accepted (implemented 2026-10-10, DS-1). Supersedes ADR-0009.

Context

The brief alternates light and dark materials deliberately: paper sections, concrete panels, graphite and night rooms, signage plates in yellow and blue, botanical accents. That isn’t a theme a visitor toggles; it is composition. The brief’s palette also fails WCAG when used naively: gray on off-white is 3.86:1, transit blue on graphite 2.16:1, yellow on off-white 1.51:1.

Decision

  • A surface is a material a section declares with data-surface="…": paper (default), concrete, graphite, night, botanical, signal-yellow, signal-blue, signal-red. It paints its background and rebinds every semantic role (fg, fg-muted, rule, rule-strong, action, action-fill(-fg), focus, status-*, surface-alt) for its descendants. Components read roles only.
  • Roles are derived: a primitive moved in OKLCH lightness (hue and chroma kept) until it reaches the target contrast against surface-alt, the harder of the surface’s two tones. Any role can be hand-overridden per surface.
  • scripts/check-contrast.ts asserts every promised pair on every surface in CI.
  • No prefers-color-scheme dark mode. The page composition decides tone.

Alternatives considered

  • Light/dark themes: a dark-mode paper section would no longer be paper. The composition is the design.
  • Hand-picked hex per role per surface (~70 values): brittle. A primitive change ripples nowhere, and nothing guarantees the pairs still pass.

Consequences

  • signal-red (added 2026-10-10): the brief’s red as a surface gave paper text only 4.54:1 and derived “muted” text lighter than primary. The surface uses the same hue/chroma darkened to 6:1 against paper (#B21B1E). The checker now also asserts hierarchy: fg-muted must be quieter than fg on every surface.

  • The first run of the checker caught a real defect: muted text derived against the surface failed on its panel tone. This is why derivation targets surface-alt.

  • Derived values sometimes shift hue perception slightly (e.g. graphite’s link blue is #5A9CEE). Overrides exist for when the eye disagrees with the math.