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:
- Read
packages/lumina/tokens/dark.css. - For each
--rf-*: value; declaration, mirror it into modes.dark.<path> in the new config. - 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-185 —
PartialTokenContract 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.