Theming and presets
The whole look comes from one tokens file, tokens/default.json. A build step turns it into CSS custom properties (tokens.css) and a Tailwind v4 theme (theme.css). At runtime nothing is computed in JavaScript: five attributes only remap CSS variables.
| Attribute | Values | Default (no attribute) |
|---|---|---|
data-theme |
light, dark |
follows prefers-color-scheme |
data-accent |
durazno, menta, cielo, lavanda, orquidea, grafito |
Coral |
data-style |
soft, bordered, elevated, flat, glass, brutal |
soft |
data-density |
compact (x0.875), normal, comfortable (x1.125) |
normal |
data-radius |
none, sm, md, lg, full |
the preset’s own radii |
<html data-theme="dark" data-style="brutal" data-accent="cielo" data-density="comfortable"> ... <section data-style="glass">only this subtree is glass</section></html>Style presets
Section titled “Style presets”Each preset changes shape, edge and elevation, not the palette:
soft: hairline shadows instead of borders, pill buttons (the default).bordered: crisp 1px borders, no shadows, tighter radii.elevated: real shadows and a lifted card surface; has light and dark variants.flat: no shadows and no borders; surfaces separate by tone only.glass: translucent surfaces with backdrop blur; has light and dark variants.brutal: thick borders, hard offset shadows, square corners.
Precedence
Section titled “Precedence”On a single element the order is source order at equal specificity: theme, then accent, then data-style, then data-density, then data-radius. data-radius also wins over a preset set on a descendant, because presets read var(--radius-card-override, <own radius>). Presets never touch density tokens. There is no !important anywhere.
Density
Section titled “Density”Density is a unitless --density that scales control heights, control and card padding and the gap-density* scale with calc(). Tailwind’s global --spacing scale is not scaled.
Using tokens in your own code
Section titled “Using tokens in your own code”Use the semantic utilities so your UI follows the presets too:
| Concern | Utilities |
|---|---|
| Radius | rounded-button, rounded-input, rounded-card, rounded-popover, rounded-chip… |
| Edge | border-card, border-control |
| Shadow | shadow-card, shadow-raised, shadow-overlay, shadow-control |
| Surface | bg-surface-card, bg-surface-control, bg-surface-overlay, bg-surface-sunken |
| Density | h-control, h-input, px-control-x, p-card, gap-density |
| Focus and motion | focus-visible:focus-ring, pressable, ease-style |
The complete list of tokens, with light and dark values and usage notes, is generated on the Design tokens page.
Your own tokens
Section titled “Your own tokens”Copy tokens/default.json, edit values (keep the same names) and run the CLI:
npx ui-theme build my-tokens.json --out src/themeIt writes tokens.css, theme.css, fonts.css and a Compose Theme.kt so a Kotlin app can share the tokens. Import your generated files instead of the shipped ones.
Runtime switching with persistence
Section titled “Runtime switching with persistence”const root = document.documentElement;root.dataset.style = "glass";root.dataset.accent = "menta";localStorage.setItem("preset", JSON.stringify({ style: "glass", accent: "menta" }));Restore it from an inline script in <head> before first paint to avoid a flash. This site does exactly that.