Design Tokens
Headless Design Tokens; covers CSS cascade layers, CSS custom properties, semantic feedback tones.
| Package | @vizejs/ui/theme |
| Maturity | stable |
| Own the source | vize lib pull theme |
| Requires | — |
| Aliases | design tokens, semantic tokens, feedback tones, cascade layers, presets, headless, atelier, midnight, paper, play, signal, high-contrast |
| Covers | CSS cascade layers, CSS custom properties, semantic feedback tones, light-dark() color scheme, forced-colors adaptations |
API
setThemeTokens
Override theme tokens on one element's subtree.
function setThemeTokens( element: ElementCSSInlineStyle, overrides: ThemeTokenOverrides, ): () => void
themeCascadeLayerOrder
Cascade layers shipped by the package, in ascending priority order.
const themeCascadeLayerOrder
themePresetAttribute
Attribute whose space-separated values opt a subtree into presets.
const themePresetAttribute
themeDensityAttribute
Attribute that retunes the density factor for a subtree.
const themeDensityAttribute
themePresets
Presets shipped in @layer vize.preset, ordered from least to most opinionated.
const themePresets: readonly ThemePresetName[]
themeDensityScales
Density factors mirrored from the data-vize-density scopes in theme.css.
const themeDensityScales: Readonly<Record<ThemeDensityScale, string>>
themeScopeStorageKeys
Storage keys read by createThemeBootstrapScript.
const themeScopeStorageKeys: Readonly<Record<ThemeScopeStorageKeyName, string>>
themeScopeAttributes
Normalize theme scope options into renderable attributes.
function themeScopeAttributes( scope: ThemeScopeOptions = {}, ): Readonly<ThemeScopeAttributes>
applyThemeScope
Apply theme scope attributes to one element and return a restorer. This is the imperative counterpart to rendering themeScopeAttributes in Vue. Only provided attributes are touched; unrelated consumer attributes and nested scopes remain owned by the application.
function applyThemeScope(element: Element, scope: ThemeScopeOptions): () => void
createThemeBootstrapScript
Create a small inline script that applies persisted theme attributes before paint. The script validates stored values against this package's published preset and density lists, falls back to server-rendered defaults when storage is empty or blocked, and otherwise does nothing on non-browser runtimes.
function createThemeBootstrapScript(options: ThemeBootstrapOptions = {}): string
themeTokenPackNames
Independent token packs that can be composed with any preset.
const themeTokenPackNames: readonly ThemeTokenPackName[]
themeTokens
Semantic token contract mirrored from theme.css. The packaged stylesheet is the source of truth; this mirror exists so JavaScript consumers can read the same headless defaults the CSS ships with. Preset values are deliberately not mirrored: presets are opt-in cascade, not API.
const themeTokens: Readonly<Record<ThemeTokenName, string>>
themeTokenProperty
Custom-property name (--vize-ui-*) for one theme token.
function themeTokenProperty(name: ThemeTokenName): string
themeTokenVar
var() reference to one theme token for imperative style composition.
function themeTokenVar(name: ThemeTokenName): string
themeTokensForPack
Tokens in one independent token pack, derived from the shared token mirror.
function themeTokensForPack(pack: ThemeTokenPackName): readonly ThemeTokenName[]
Behavior
Normative contract for the theme family (@vizejs/ui/theme): the semantic
design-token contract, the package-wide cascade-layer order, and the opt-in
presets. The stylesheet contract is proven on the packaged dist/style.css
and the CSS-only dist/theme.css / dist/theme-preset-*.css entrypoints
(the pack pipeline lowers src/families/foundations/theme/theme.css and the preset files
src/families/foundations/theme/theme-preset-headless.css, src/families/foundations/theme/theme-preset-atelier.css,
src/families/foundations/theme/theme-preset-midnight.css, src/families/foundations/theme/theme-preset-paper.css,
src/families/foundations/theme/theme-preset-play.css, src/families/foundations/theme/theme-preset-signal.css, and
src/families/foundations/theme/theme-preset-high-contrast.css to the declared browser floor; see
style-pipeline.behavior.md) in
src/families/foundations/theme/theme-stylesheet.test.ts; runtime rows are proven by the named test in
src/families/foundations/theme/theme.test.ts or src/families/foundations/theme/theme-ssr.test.ts; compile-only assertions live
in src/families/foundations/theme/theme.types.test-d.ts.
| # | State | Input | Outcome | Proven by |
|---|---|---|---|---|
| T1 | packaged stylesheet | consumer imports any styled entry | cascade layers ship in ascending priority order vize.tokens → vize.ui → vize.preset → vize.policy |
ships the documented cascade layer order |
| T2 | packaged stylesheet | any styled entry | the semantic token contract ships in @layer vize.tokens at zero specificity, matching the TS mirrors |
ships layered zero-specificity theme tokens matching the mirrors |
| T3 | packaged stylesheet | no data-vize-theme attribute or data-vize-theme~="headless" |
headless default and explicit preset stay on the system palette, flat, and free of visual token opinion | keeps the headless default free of visual opinion |
| T4 | packaged stylesheet | data-vize-density="compact" | "comfortable" |
the density factor retunes, scaling every space and control-size token in the subtree | ships density scopes that retune the shared factor |
| T5 | packaged stylesheet | data-vize-theme~="headless" | "atelier" | "midnight" | "paper" | "play" | "signal" | "high-contrast" |
published presets are scoped to their opt-in attribute; only non-headless presets assign visual tokens | scopes published presets to their opt-in attributes |
| T6 | packaged stylesheet | light and dark schemes | preset light-dark() values ship lowered to the floor and follow the user's color scheme |
lowers preset color schemes to the declared floor |
| T7 | packaged stylesheet | forced-colors: active |
@layer vize.policy snaps color roles to the system palette and flattens elevation, beating presets |
stands down to system colors under forced colors |
| T8 | any | setThemeTokens(element, overrides) |
overrides apply to the element inline and the restore callback reinstates values | applies and restores token overrides on a real element |
| T9 | any | unknown token name or empty value | themeTokenProperty/setThemeTokens throw VIZE_UI_THEME_TOKEN |
rejects unknown tokens and empty override values |
| T10 | mounted | consumer binds scope attributes and tokens | preset and density attributes render, and scoped overrides apply through the mounted DOM | scopes presets and densities in a mounted consumer |
| T11 | SSR | render with token helpers in setup | byte-identical markup; no platform global is required | renders byte-identical SSR markup without platform globals |
| T12 | SSR | hydration | hydrating a themed consumer emits no diagnostics and keeps server nodes | hydrates a themed consumer without replacement or diagnostics |
| T13 | public types | invalid token names or mutated records | compilation rejects misuse | src/families/foundations/theme/theme.types.test-d.ts |
| T14 | any | themeTokensForPack(pack) |
every token belongs to exactly one independent pack, with no runtime-maintained duplicate token graph | publishes independent token packs without gaps or overlap |
| T15 | any | themeScopeAttributes() / applyThemeScope() |
nested theme scopes validate, deduplicate, apply, and restore preset and density attributes | normalizes nested theme scope attributes... |
| T16 | browser boot | createThemeBootstrapScript() before paint |
persisted preset and density choices apply to <html>; blocked storage and invalid values fall back safely |
creates a storage-backed no-flash theme bootstrap script |
| T17 | source presets | every opinionated preset source | each preset owns the complete scheme-aware semantic color and feedback tone matrix plus elevation roles | keeps every opinionated preset on the complete semantic color matrix |
| T18 | packaged stylesheet | forced-colors: active |
the policy layer ships after presets and covers every visual color/elevation token | keeps the forced-colors policy above the complete visual token surface |
| T19 | packaged stylesheet | feedback tone token names | neutral, info, success, warning, danger, and their contrast roles ship through defaults/presets |
ships semantic feedback tone tokens through every theme surface |
| T20 | CSS-only exports | consumer imports @vizejs/ui/theme.css or one @vizejs/ui/theme-preset-*.css |
the entry resolves to lowered CSS only, carries the layer-order prelude, and does not retain JavaScript | theme CSS entrypoints are lowered CSS assets without runtime entry shims |
Cascade-layer order and specificity budget
- The package ships exactly four cascade layers, in ascending priority:
vize.tokens(semantic defaults) <vize.ui(behavior-critical component styles) <vize.preset(opt-in visual presets) <vize.policy(accessibility stand-downs such as forced colors).src/families/foundations/theme/theme.cssdeclares the order once; packaged CSS concatenates insrc/index.tsmodule order, so the theme export stays directly afterfield-wiringand the statement leadsdist/style.css. - Specificity budget: every rule in every vize layer sits on a
:where()selector at specificity(0,0,0)— pseudo-elements keep only their intrinsic element specificity. Consumer CSS outside the vize layers always wins, and consumer@layerrules win by declaring their layers later.
Extension contract
- Every token is a
--vize-ui-*custom property defined at zero specificity: a consumer rule outside the layers — or asetThemeTokensoverride on an ancestor — replaces any role for a whole subtree without forking components.--vize-ui-focus-ring-colorresolves through--vize-ui-color-accent, and space/size tokens resolve through--vize-ui-density, so one override retunes a whole phase. - Feedback tones are color tokens:
--vize-ui-color-neutral,--vize-ui-color-info,--vize-ui-color-success,--vize-ui-color-warning,--vize-ui-color-danger, and each*-contrastpairing. Headless defaults stay system-palette aligned, while presets make the tones visually distinct for badge, alert, status, and future feedback chrome. data-vize-themeholds space-separated preset names and is inert without the shipped preset CSS;headlessis an explicit no-visual-opinion preset for persisted theme choice,atelieris the Vize brand default,midnightis the dark-first application preset,paperis the editorial preset for content-forward surfaces,playis the expressive consumer-product preset,signalis the dense data/tooling preset, andhigh-contrastis the legibility-first preset with heavy boundaries and flat elevation. When multiple presets are present, package source order resolves ties, so later shipped presets win. Every opinionated preset must assign every semantic color role with a scheme-aware value, plus every elevation role, so a new preset cannot accidentally ship a partial visual surface.data-vize-densityacceptscompactandcomfortable. Both scope by inheritance, so nesting attributes nests themes.- Consumers that want manual CSS loading can import
@vizejs/ui/theme.cssfor the semantic token and policy layers, then one or more@vizejs/ui/theme-preset-*.cssfiles for visual presets. The publicThemeTokenStylesheetSpecifierandThemePresetStylesheetSpecifiertypes mirror those package subpaths for typed integration code without adding runtime weight. - SSR bootstrapping is CSS-only and flash-free by construction: server-render
the attribute (typically on
<html>), and presetlight-dark()values follow the user's scheme with no runtime script. For persisted user choices,@vizejs/ui/theme-scopecreates an optional inline bootstrap script that validates stored values before first paint, falls back to server-rendered defaults, and imports no CSS. themeTokensForPack()exposes color, typography, space, size, radius, border, elevation, opacity, z-index, focus, and density packs from the single token mirror. The package does not publish a second runtime token graph for packs, so every new token must remain covered by the gap/overlap test. Motion tokens stay in the motion family (motion.behavior.md).