Skip to main content
Urbicon UI

Token Reference

The tokens the library ships, layer by layer: color ramps, spacing, typography, radius, motion and depth. Look values up here; write and switch themes on the Themes page.

Token Architecture

Three layers. The foundation holds the raw ramps a theme re-tints; the semantic layer maps them to roles like surface-elevated and resolves both modes via light-dark(); the interaction layer times motion and stacks depth. How to write, scope and switch a theme is the Themes page; this one lists what exists.

Foundation Colors

@theme {
  /* Foundation Colors (oklch for better perception) */
  --color-neutral-50: oklch(0.965 0.006 240);
  --color-neutral-500: oklch(0.55 0.016 240);
  --color-neutral-900: oklch(0.15 0.012 240);

  /* Intent 500s darkened so white text passes WCAG AA */
  --color-primary-500: oklch(0.58 0.15 240);
  --color-success-500: oklch(0.5 0.15 140);
  --color-danger-500: oklch(0.5 0.17 25);
}

Semantic Tokens

/* Semantic Layer – maps foundation to
   roles; light-dark() resolves both modes */
--color-surface-base: light-dark(
  var(--color-neutral-0), var(--color-neutral-900));
--color-text-primary: light-dark(
  var(--color-neutral-900), var(--color-neutral-100));
--color-primary: light-dark(
  var(--color-primary-600), var(--color-primary-500));

/* Interactive states */
--color-interactive-hover:
  oklch(from var(--color-primary-500) l c h / 0.1);
--color-interactive-focus: light-dark(
  var(--color-primary-500), var(--color-primary-400));

Interaction Layer

/* Interaction Layer – motion & depth */
--blocks-duration-fast: 150ms;
--blocks-duration-normal: 250ms;
--blocks-ease-gentle: cubic-bezier(0.25, 0.1, 0.25, 1);

--blocks-shadow-sm: var(--blocks-shadow-scale-sm);
--blocks-shadow-md: var(--blocks-shadow-scale-md);

Override at the right layer

  • Re-tint a hue → foundation ramp. Override every stop of the ramp, keeping each stop's lightness/chroma profile: the WCAG-tuned contrast survives, and the semantic layer picks the new hue up in both modes automatically. This is exactly what the shipped themes do.
  • Change a role → semantic token. To alter what "elevated" or "subtle" means, override the semantic token itself, and supply a light-dark(light, dark) pair, otherwise the token is pinned to one look in both modes.
  • Restyle one component → the component API, not CSS. That is a job for class, slotClasses and BlocksProvider, never for ! color overrides; the Customization hub carries the decision table.

Custom theming and dark mode used to live on this page. They moved to Themes → Write Your Own Theme and Themes → Dark Mode.

Color System

Neutral Palette

The neutral ramp has 16 steps: alongside 0 (pure white) and the standard 50–950 ladder it ships a 25 tint (the quiet-surface ground) and the half-steps 650/750/850, which differentiate the dark-mode elevation ladder.

0
25
50
100
200
300
400
500
600
650
700
750
800
850
900
950

Intent Ramps

Six intent ramps (primary, secondary, success, warning, danger, info), each a full 50–950 ladder. The ramps are tuned so text-on-fill clears WCAG AA on every solid fill: white on the 500/600/700 fills in light mode, near-black on the lighter 400/500 fills in dark mode. It is the label colour for every solid intent, which is why it is not named after one of them. text-on-primary still exists, resolves to the same value and governs the primary fill alone, so retuning it cannot repaint success or danger along with it. Warning is the deliberate exception (its fill stays light in both modes and pairs with its own warm-dark text-on-warning).

primary

--color-primary-50
--color-primary-500
--color-primary-900

secondary

--color-secondary-50
--color-secondary-500
--color-secondary-900

success

--color-success-50
--color-success-500
--color-success-900

warning

--color-warning-50
--color-warning-500
--color-warning-900

danger

--color-danger-50
--color-danger-500
--color-danger-900

info

--color-info-50
--color-info-500
--color-info-900

Intent Roles

The primary intent carries 6 roles, listed below; every other intent carries them under the same suffixes, and the gaps are listed under the table. Solid things (a button, a badge, a progress bar) take the base fill, bg-primary, with text-text-on-fill as the label on it. Text in the intent's colour on a reading surface (a page, a card, a panel) or on the intent's own -subtle tint takes text-primary-text, the stop that clears AA there. Tailwind also emits text-primary from the fill token and nothing flags it; as text it lands below AA (4.28:1 on bg-primary-subtle), so write text-primary-text. text-primary-emphasis is the near-ink step for a heading or a strong statement. The two classes that look alike do opposite jobs: text-primary-text is text in the primary colour, text-text-on-fill (or its primary-only alias text-text-on-primary) is the label on a primary fill. The swatches render in whatever theme is active, on this site the docs skin's own primary; the light and dark stops under each token are the library's own.

TokenUtilityPurpose
--color-primary light primary-600 (L 0.52) · dark primary-500 (L 0.58)bg-primarythe fill — a surface with text-on-fill sitting on it; never a text colour
--color-primary-hover light primary-700 (L 0.44) · dark primary-400 (L 0.66)bg-primary-hoverthe hover step of the fill
--color-primary-active light primary-800 (L 0.36) · dark primary-300 (L 0.74)bg-primary-activethe pressed step of the fill
--color-primary-subtle light primary-50 (L 0.95) · dark primary-900 (L 0.28)bg-primary-subtlea soft tinted ground in the intent's hue — an Alert's background, a selected row
--color-primary-text light primary-700 (L 0.44) · dark primary-400 (L 0.66)text-primary-textthe intent as text on a reading surface — the nearest stop that clears AA 4.5:1 on every reading surface and the intent's own -subtle
--color-primary-emphasis light primary-900 (L 0.28) · dark primary-200 (L 0.82)bg-primary-emphasistext-primary-emphasisthe near-ink tier — strong statements as text, and doubles as a fill
  • neutral has no -text token: its base already clears AA as text on every ground in every theme, so a -text role would only alias the base
  • warning -text: the light stop is -800, the only rung of a yellow ramp that reads on white — it lands amber-brown while the fill stays yellow
  • danger -text: the light stop is the fill's stop, red already reads on paper; dark needs -300 because sRGB red is too dim on the lightened panels at -400

Run urbicon css-reference intents (the urbicon CLI) for the stops of every other intent.

Neutral Intent Chrome

The neutral intent (bg-neutral, text-neutral, neutral borders) is built from a separate --color-warm-neutral-* ramp, but never renders it directly: each role re-derives the ramp stop through oklch(from … l c var(--neutral-chrome-hue)), keeping the ramp's lightness and chroma and taking only the hue from the knob. The knob defaults to 240, so the library's own chrome is cool, and re-tinting it is a one-line theme move (see Motion & Depth). The swatches below render in whatever theme is active: this docs site re-pins them to the warm ramp directly, which is why they look warm here.

--color-neutral
--color-neutral-hover
--color-neutral-active
--color-neutral-subtle
--color-neutral-emphasis

Your own color tokens live in the same system: any --color-* variable declared in @theme gets its Tailwind utilities for free.

Custom brand token

@theme {
  /* bg-brand-500, text-brand-500, border-brand-500, … */
  --color-brand-500: oklch(0.5 0.25 45);
}

Spacing Scale

Urbicon UI does not ship a custom spacing token layer. It uses Tailwind's built-in spacing scale directly. Apply spacing with the standard utilities (p-4, gap-2, m-6). Each step is 0.25rem × n, driven by Tailwind's own --spacing variable.

UtilityValuePixelsVisual
p-0 / gap-000px
p-1 / gap-10.25rem4px
p-2 / gap-20.5rem8px
p-3 / gap-30.75rem12px
p-4 / gap-41rem16px
p-6 / gap-61.5rem24px
p-8 / gap-82rem32px
p-12 / gap-123rem48px

Typography Scale

Sizes, weights, leading, tracking and families are Tailwind @theme variables; override them alongside your color ramps (Themes → Typography, including the paired --text-*--line-height rule). The two sub-xs steps are library-added and ship size-only; every other row re-tunes a Tailwind built-in.

UtilityOverrideValuePixelsUses in blocksExample
text-3xs--text-3xs0.625rem10px17The quick brown fox
text-2xs--text-2xs0.6875rem11px23The quick brown fox
text-xs--text-xs0.75rem12px130The quick brown fox
text-sm--text-sm0.875rem14px170The quick brown fox
text-base--text-base1rem16px96The quick brown fox
text-lg--text-lg1.125rem18px39The quick brown fox
text-xl--text-xl1.25rem20px14The quick brown fox
text-2xl--text-2xl1.5rem24px5The quick brown fox

Weights

UtilityOverrideValueUses in blocks
font-normal--font-weight-normal4006
font-medium--font-weight-medium50071
font-semibold--font-weight-semibold60053
font-bold--font-weight-bold70012

Border Radius

Physical Scale

Real --radius-* CSS variables, each with a matching Tailwind utility.

rounded-xs
0.125rem
rounded-sm
0.25rem
rounded-md
0.375rem
rounded-lg
0.5rem
rounded-xl
0.75rem
rounded-2xl
1rem
rounded-3xl
1.5rem
rounded-4xl
2rem

Semantic Tiers

Components consume a 3-tier semantic vocabulary, not raw radii. Re-tint the three tier variables to reshape the whole library at once (the full cascade: Radius Tiers). A fourth token, --radius-bridge, is not a tier: it pairs pill triggers with their dropdown panels.

TokenValueUsed by
--radius-commit9999pxPill/round — actions, identity, status (Button, Badge, Toggle)
--radius-control9999pxRadio indicator only. Defaults to the same pill as --radius-commit but is not tied to it, so squaring your buttons keeps the "pick exactly one" circle. Set it to 0 if you want square radios too.
--radius-modifyvar(--radius-sm) · 4pxEditable surfaces, navigation (Input, Select, Tab, Menu)
--radius-containvar(--radius-xs) · 2pxContainers, panels (Card, Alert, Dialog, Drawer, Popover, Tooltip)
--radius-bridgevar(--radius-md) · 6pxThe middle rung, for two cases: adjacency (a floating panel anchored to a pill trigger — the Menu panel) and optical size (a surface too small for the container radius to read as intentional — the ChatMessage bubble, Textarea at tier="commit", Card tier="bridge")

Motion & Depth

The interaction layer ships real --blocks-duration-* and --blocks-shadow-* variables (plus easing curves like --blocks-ease-gentle). Durations collapse to 1ms under prefers-reduced-motion; prefers-contrast: more widens the focus ring to 3px and darkens subtle borders to text colour; in print, shadows drop to none and hairlines promote to a real grey rule.

Duration tokenValue
--blocks-duration-instant75ms
--blocks-duration-fast150ms
--blocks-duration-normal250ms
--blocks-duration-slow350ms
--blocks-duration-slower500ms
--blocks-duration-slowest750ms
Shadow tokenSource
--blocks-shadow-xsvar(--blocks-shadow-scale-xs)
--blocks-shadow-smvar(--blocks-shadow-scale-sm)
--blocks-shadow-basevar(--blocks-shadow-scale-base)
--blocks-shadow-mdvar(--blocks-shadow-scale-md)
--blocks-shadow-lgvar(--blocks-shadow-scale-lg)
Easing tokenValue
--blocks-ease-linearlinear
--blocks-ease-gentlecubic-bezier(0.25, 0.1, 0.25, 1)
--blocks-ease-springycubic-bezier(0.68, -0.55, 0.265, 1.55)
--blocks-ease-confidentcubic-bezier(0.4, 0, 0.2, 1)
--blocks-ease-bouncecubic-bezier(0.68, -0.6, 0.32, 1.6)
--blocks-ease-smoothcubic-bezier(0.4, 0, 0.6, 1)
--blocks-ease-snappycubic-bezier(0.4, 0, 0.2, 1)
Override pointDefault
--blocks-overlay-enter-duration200ms
--blocks-overlay-exit-duration180ms
--blocks-overlay-backdrop-enter-duration200ms
--blocks-overlay-backdrop-exit-duration180ms
--blocks-overlay-easingcubic-bezier(0.83, 0, 0.17, 1)
--blocks-tooltip-durationvar(--blocks-duration-fast)
--blocks-tooltip-easingvar(--blocks-ease-confident)
--blocks-popover-durationvar(--blocks-duration-fast)
--blocks-popover-easingvar(--blocks-ease-confident)
--blocks-collapse-durationvar(--blocks-duration-normal)
--blocks-collapse-easingvar(--blocks-ease-smooth)
--blocks-focus-ring-width2px
--blocks-focus-ring-offset2px
--blocks-focus-ring-colorvar(--color-primary)
--blocks-press-scale0.98

Theme-level chroma knobs

Two tokens let a theme match its chrome to its chassis without touching contrast. The four coloured themes set both; see blocks/src/lib/style/themes/forest.css. The Neutral theme sets neither, by design: it inherits the library-default cool grey chrome and leaves the shadow tint untouched.

TokenDefaultWhat it does
--blocks-shadow-tint0 0 0Re-tint so shadows match your chassis. Declare on :root, see below.
--neutral-chrome-hue240Hue of the neutral intent chrome (bg-neutral / text-neutral / neutral borders). Keeps the warm-neutral ramp lightness; only the hue moves, so contrast is untouched.
Set these in :root, never in @theme. Both are raw partial values, not standalone tokens — --blocks-shadow-tint is an oklch L C H triplet without an alpha channel, spliced into oklch(var(--blocks-shadow-tint) / 0.05). Putting either inside @theme gets you nothing. The shipped themes declare them in a :root block after their @theme for exactly this reason.

The tint also only bites at the root. It is read inside the value of --blocks-shadow-*, which is declared on :root, so it is substituted there and every descendant inherits a finished shadow — setting --blocks-shadow-tint on a scoped element moves nothing. A scope that needs its own tint has to re-declare the five steps beside it.