Foundations
Themes
One configuration generates a theme. Attributes choose its mode, density, and profiles on any element, and the scopes nest in any order, across shadow roots.
Same roles, paired appearances
Each mode-dependent role is one expression switched by two inherited toggles, so mode, product, and density scopes nest in any order. The page follows prefers-color-scheme unless data-odd-mode is set; data-odd-mode="system" restores the system behaviour inside an explicit subtree.
color.canvascolor.surface.defaultcolor.text.primarycolor.action.default
color.canvascolor.surface.defaultcolor.text.primarycolor.action.default
Scope contract
Density changes spacing and control sizing only, never font sizes, and never takes a target below the documented minimum. The scopes are plain attributes on any element: data-odd-mode, data-odd-theme, data-odd-product, and data-odd-density, with data-odd-profile for profiles.
| Scope | Tokens it sets | Aliases it re-declares |
|---|---|---|
[data-odd-mode] | 1 | 40 |
[data-odd-density="compact"] | 13 | 12 |
[data-odd-profile~="navigation.compact"] | 4 | 0 |
[data-odd-profile~="layout.compact"] | 2 | 1 |
Composition and precedence
The generated CSS has no rule per combination of scope values: each product, density, and profile is one rule, and mode is two toggles. A profile, or a product with its own colour roles, adds one rule that re-applies it inside nested scopes.
| Situation | Result |
|---|---|
| Axes on nested elements (any order, any depth, across shadow roots) | The nearest scope of each axis applies; each scope re-declares its alias closure, so outer scopes of other axes keep working (§5, §5.3) |
| Several axes on one element | Rules have equal specificity; emission order decides overlapping roles: products, then densities, then profiles, all after the mode rules. In practice only densities and profiles overlap (both touch layout and component roles), and the profile wins |
| Two profiles on one element (data-odd-profile="a b") setting the same role | The profile that comes later in the configuration wins; the attribute's list order does not matter. Reported as profile.overlap with both paths |
| A profile on an ancestor and a density nested inside it | The profile still applies (its re-application selector) |
| A mode scope inside a product with its own colour roles | The product's roles apply (its re-application selector, 0,2,0); a profile's re-application, emitted later, wins where both set a role |
| A product nested inside a product that overrides roles | Not supported: mode scopes inside the inner product take the outer product's roles |
| A nested data-odd-theme | Re-declares every token: outer products, densities, and profiles stop applying inside it; mode toggles still come from outside |
| overrides vs. scopes | Overrides change the base values every scope starts from; a scope that re-declares a role still wins inside it |
Create a theme
Theme Studio, part of @oddkin/workbench, edits a theme configuration, runs the same generator in the browser, previews the components in isolated scopes, and exports the configuration and its CSS. The theming guide shows how a page applies a theme.