WORK-188
Setting up your dashboard 0 entities found · 8/33 branches scanned
ID:WORK-188Status:done

Dark mode as PartialTokenContract overlay

Replace today's parallel packages/lumina/tokens/dark.css file (which duplicates ~30 tokens under two selectors — [data-theme="dark"] and @media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) }) with a structured modes.dark overlay in ThemeTokensConfig. Authors specify only the tokens that differ from base; everything else inherits via CSS variable cascade. Future modes (high-contrast, sepia, print) reuse the same shape.

Priority:highComplexity:mediumMilestone:v0.14.0Source:SPEC-048

Criteria completion

Criteria completion: 2 of 7 (29%) checked; history from May 18 to May 180%25%50%75%100%May 18May 18
Branches 3
History 5
  1. 116c321
    statusin-progressdone
    by bjornolofandersson
  2. 908d19f
    • ☑ `ThemeTokensConfig.modes` field accepts a record of mode name → `PartialTokenContract`
    • ☑ Authors only specify *changed* tokens in a mode overlay — unspecified tokens inherit from base via the CSS variable cascade
    by bjornolofandersson
  3. ed12113
    Content editedby Claude
    v0.14.0 Chunk 2: SPEC-048 pipeline (WORK-187, 188, 190 — machinery)
  4. 3b92415
    Created (ready)by bjornolofandersson
  5. f73346a
    Content editedby Claude
    plan: add v0.14.0 milestone and SPEC-048 work items (WORK-185 to 191)

Acceptance Criteria

  • ThemeTokensConfig.modes field accepts a record of mode name → PartialTokenContract
  • Build pipeline emits per-mode stylesheets scoped to [data-theme="<mode>"] and to the @media (prefers-color-scheme: <mode>) { :root:not([data-theme]) } block for matching system preference — generateThemeStylesheet emits both blocks for dark/light, the explicit selector only for custom modes
  • Authors only specify changed tokens in a mode overlay — unspecified tokens inherit from base via the CSS variable cascade
  • Lumina's existing dark.css migrated to modes.dark config form; resulting stylesheet renders identically (visual regression check) (deferred to Chunk 3 / WORK-191)
  • Generated stylesheet output order is deterministic so diffs in CI stay clean — verified in tests
  • Mode overlay validation rejects keys not present in TokenContract with clear errors — validateThemeTokensConfig walks modes.<name> entries against the same shape and emits modes.dark.color.primery style paths
  • Documentation note explaining how to add a custom mode (e.g. high-contrast) — single config snippet, no parallel CSS file required (deferred to WORK-210 migration note in Chunk 8)

Approach

Reshape the existing dark-mode CSS into config form. The migration is mechanical for Lumina:

  1. Read packages/lumina/tokens/dark.css.
  2. For each --rf-*: value; declaration, mirror it into modes.dark.<path> in the new config.
  3. Delete the raw CSS file once the generated stylesheet matches.

The generated CSS preserves the existing two-selector pattern (explicit [data-theme="dark"] for user-toggled mode, media query for system preference). That's the SSR-friendly pattern and matches what SPEC-052's cascade resolution will emit.

Authors authoring custom themes specify only the deltas:

{
  "theme": {
    "modes": {
      "dark": {
        "color": { "primary": "#a78bfa", "text": "#f1f5f9" }
      },
      "high-contrast": {
        "color": { "border": "#000000", "text": "#000000" }
      }
    }
  }
}

Dependencies

  • WORK-185PartialTokenContract shape defined.
  • WORK-187 — base stylesheet generation infrastructure to extend.

References

  • SPEC-048 — "Modes are partials over the base, not parallel contracts" design principle
  • packages/lumina/tokens/dark.css — file being migrated and eventually removed

Resolution

Completed: 2026-05-19

Shipped in v0.14.0 Chunks 2 + 3. ThemeTokensConfig.modes accepts the partial overlay; generateThemeStylesheet emits both [data-theme="dark"], [data-color-scheme="dark"] and the @media (prefers-color-scheme: dark) block (verified by token-stylesheet.test.ts). Lumina's hand-authored dark.css continues to ship and is kept in lockstep with luminaTokens.modes.dark by a CSS-coverage test (token-config-coverage.test.ts) — the file-deletion criterion remains explicitly deferred per the work item's scope split, and the migration-note acceptance is owned by WORK-210.