variables #

Style variables, or just "variables" in fuz_css, are CSS custom properties that can be grouped into a theme. Each variable has a base value that applies in both color-schemes, a dark value that replaces it in dark mode, or both. They're design tokens with an API.

The goal of the variables system is to provide runtime theming that's efficient and ergonomic for both developers and end-users. Variables can be composed in multiple ways:

  • by CSS classes, both utility and component
  • by other variables, both in calculations and to add useful semantics (e.g. button_fill_hover uses shade_50 but can be themed independently)
  • in JS like the Svelte components in fuz_ui

Variables also provide an interface that's generally secure for user-generated content, if you're into that kind of thing.

The result is a flexible system that aligns with modern CSS to deliver high-capability UX and DX with low overhead.

Most color variables are derived: knobs feed ramp stops and ramp stops feed color stops, all computed in pure CSS (calc()/pow()/oklch()). Scale knobs like radius_scale derive other families the same way, and a derived token or color stop can still be pinned as an escape hatch. The ramp stops in between are internal (--_palette_lightness_30), so a theme can't set them. See themes for setting the rest.

In bundled mode, only the variables your code uses are emitted, along with any they depend on. The full theme.css stylesheet ships every variable, for utility-only mode and direct imports.

export type Theme = { name: string; /** What the theme looks like, for theme pickers. */ summary?: string; variables: StyleVariable[]; /** Single-scheme themes render one appearance in both color schemes. */ scheme?: 'dual' | 'light' | 'dark'; }; export type StyleVariable = { name: string; base?: string; dark?: string; summary?: string; };

Both are @fuzdev/fuz_schema schemas, so the same names validate at runtime: s.parse(Theme, value) reports what's wrong, parse_theme(value) returns a theme or null, and validate_theme(theme) in theme_validate.ts runs the full lint.

Hooks
#

Hooks are the contextual surface: custom properties the base styles and classes read but don't declare as variables, so they have no :root default. Set one on an element to retune what reads it there, with a literal class like --flow_margin:0 or in a component's styles - the sized_* composites work this way, setting --flow_margin and --font_size for their subtree. Where nothing sets a hook, its read falls back to the value listed. A local hook is declared by the rule that reads it, so a value reaches it only from a rule on the same element.

Hooks aren't theme variables: from a theme's root, an inherited hook retunes every element that reads it and a local one reaches nothing, so validate_theme warns on a theme that sets one. Themes turn the knobs instead. The catalog is style_hooks.ts.

hookread byfallback
--flow_marginblock elements, headings, .mb_flow, .mt_flow
var(--space_lg)
--font_sizeheadings, .heading, button, input[type='checkbox']::before, .chip
inheritvar(--font_size_xl3)revert-layer
--icon_size.menuitem .icon
var(--icon_size_md)
--menuitem_padding.menuitem
var(--space_xs3) var(--space_xs)
--menuitem_min_height.menuitem
var(--input_height_compact)
--chip_padding_x.chip
var(--space_xs)
--border_radiusa, aside, code, summary, input, textarea, select, button, .pane, .panel, .chip
var(--border_radius_xs3)var(--border_radius_xs)var(--control_radius)
--fillbutton
var(--shade_50)var(--shade_60)
--button_fillbutton, .selectablelocal
--button_fill_hoverbutton, .selectablelocal
--button_fill_activebutton, .selectablelocal
--button_text_colorbuttonlocal
--button_border_colorbuttonlocal
--button_border_color_hoverbuttonlocal
--button_border_color_activebuttonlocal
--button_min_heightbutton, button.inline, p button
var(--input_height)var(--input_height_compact)
--shadowbutton
var(--button_shadow)
--select_arrow_sizeselectlocal
--thumb_sizeinput[type='range']local
--thumb_background_colorinput[type='range']local
--checkbox_contentinput[type='checkbox']::beforelocal
--checkbox_content_emptyinput[type='checkbox']
''
--checkbox_content_checkedinput[type='checkbox']
'✓'
--overflowinput[type='checkbox']::before
visible
--clickable_transform.clickable
scale3d(1, 1, 1)
--clickable_transform_focus.clickable
scale3d(1.07, 1.07, 1.07)
--clickable_transform_hover.clickable
scale3d(1.1, 1.1, 1.1)
--clickable_transform_active.clickable
scale3d(1.2, 1.2, 1.2)
--clickable_transform_origin.clickable
center
--clickable_transition_duration.clickable
0s
--shadow_color.shadow_*, .shadow_top_*, .shadow_bottom_*, .shadow_inset_*
var(--shadow_color_umbra)
--shadow_alpha.shadow_*, .shadow_top_*, .shadow_bottom_*, .shadow_inset_*
var(--shadow_alpha_30)var(--shadow_alpha_40)var(--shadow_alpha_50)var(--shadow_alpha_60)var(--shadow_alpha_70)

All 511 style variables
#