# v2 design tokens The v2 UI kit is built on a small set of **numbered token scales** sourced from Radix and the "Probo Radix UI" Figma file. Every token family follows the same convention as color — a numbered scale exposed as Tailwind utilities — so the kit speaks one consistent visual language: `bg-sand-3`, `text-4`, `rounded-3`, `shadow-2`. Theme entry: [`packages/ui/src/v2/theme.css`](../../packages/ui/src/v2/theme.css), aggregating `theme/colors.css`, `theme/typography.css`, `theme/radius.css`, `theme/shadows.css`, `theme/z-index.css`. | Family | Utilities | Steps | Source | |--------|-----------|-------|--------| | Color | `bg-/text-/border--<1–12>` | 12 | Radix Colors | | Typography | `text-<1–9>` (+ font weights) | 9 | Radix type scale | | Radius | `rounded-<1–6>` | 6 | Radix "Medium" radius | | Shadow | `shadow-<1–6>`, `inset-shadow-<1–3>` | 6 / 3 | Radix elevation | | Z-index | `z-<1–6>` | 6 | UI layer roles | | Spacing | Tailwind native (`p-4`, `gap-2`, …) | — | not tokenized | Each `---*` is reset to `initial` in its theme layer, so a v2 build exposes **only** the numbered scales — Tailwind's default palette, t-shirt type sizes, and `shadow-sm/md/lg` are intentionally unavailable. Use the numbered token; never reintroduce an ad-hoc value. # Colors (Radix scale) The v2 UI kit uses [Radix Colors](https://www.radix-ui.com/colors) 12-step scales as its color primitive. Each hue provides 12 numbered steps designed for specific use cases. Components consume these through Tailwind utility classes (`bg-sand-3`, `text-red-11`, `border-gold-7`, …). ## Available scales | Scale | Role | |-------|------| | **sand** | Neutral — primary UI chrome (backgrounds, borders, text) | | **gold** | Warm accent neutral | | **red** | Destructive / error | | **green** | Success / positive | | **amber** | Warning | | **sky** | Informational | ## Step-to-usage mapping Every scale follows the same 12-step structure: | Step | Use case | Tailwind example | |------|----------|------------------| | 1 | App background | `bg-sand-1` | | 2 | Subtle background | `bg-sand-2` | | 3 | UI element background | `bg-sand-3` | | 4 | Hovered UI element background | `hover:bg-sand-4` | | 5 | Active / selected UI element background | `bg-sand-5` | | 6 | Subtle borders and separators | `border-sand-6` | | 7 | UI element border and focus rings | `border-sand-7` | | 8 | Hovered UI element border | `border-sand-8` | | 9 | Solid backgrounds | `bg-green-9` | | 10 | Hovered solid backgrounds | `hover:bg-green-10` | | 11 | Low-contrast text | `text-sand-11` | | 12 | High-contrast text | `text-sand-12` | ### Quick mental model Three bands: **low = light/background**, **middle = borders**, **high = text/solid**. - **1–2** → backgrounds - **3–5** → component backgrounds (normal → hover → active) - **6–8** → borders (subtle → default → strong) - **9–10** → solid backgrounds (normal → hover) - **11–12** → text (low-contrast → high-contrast) ## Choosing a color step 1. **What am I styling?** - Background → steps 1–5 (or 9–10 for solid fills) - Border → steps 6–8 - Text / icon → steps 11–12 2. **What state?** - Default → lower step in the range (3, 6, 9, 11) - Hover → next step up (4, 7, 10) - Active / pressed → one more (5, 8) 3. **Which hue?** - Neutral UI → `sand` - Semantic meaning → `red` (error), `green` (success), `amber` (warning), `sky` (info) - Warm accent → `gold` ## Neutral vs accent Use **sand** for all general UI chrome: page backgrounds, card backgrounds, borders, primary text. Use hue scales only when conveying semantic meaning: ```tsx // Neutral card

Title

Description

// Error state

Something went wrong

// Success badge Approved ``` ## Contrast guarantees Per the [Radix spec](https://www.radix-ui.com/colors/docs/palette-composition/understanding-the-scale), steps 11 and 12 — which are designed for text — are guaranteed to reach Lc 60 and Lc 90 APCA contrast respectively on top of a **step 2** background from the same scale. So both `text-sand-11` and `text-sand-12` are readable on `bg-sand-2`. The guarantee is documented against step 2 only; step 1 is the more extreme background (lighter in light mode, darker in dark mode), so text steps stay at least as readable there in practice, but Radix does not state it as a guarantee. ## Dark mode **Never apply dark-mode color overrides in components.** The v2 theme imports `@radix-ui/colors` CSS files which handle light/dark switching automatically. Dark mode activates when a `.dark` class is present on ``: ```ts document.documentElement.classList.toggle("dark", isDark); ``` The same Tailwind classes (`bg-sand-1`, `text-red-11`, etc.) resolve to the correct dark values automatically because the `@theme inline` mappings reference the Radix variables (`var(--sand-1)`, etc.) which switch based on the `.dark` class. P3 wide-gamut colors are included for both light and dark modes on supported displays. This is independent of v1's dark mode which uses `@variant dark` / `prefers-color-scheme`. ## Isolation from v1 v2 is a standalone theme isolated at the build level, not via a runtime DOM scope. An app opts into v2 by importing the v2 theme instead of the v1 `theme.css`: ```css /* app index.css — v2 build */ @import "tailwindcss"; @import "@probo/ui/src/v2/theme.css"; ``` The v2 theme wipes Tailwind's default palette (`--color-*: initial`), keeping only `transparent`, `black`, `white`, and the Radix scales below. Within a v2 build these color utilities are global — there is no `[data-theme="v2"]` ancestor requirement. A given build is either v1 or v2; the two do not coexist on the same page. ## Do / don't ### Use the numbered scale ```tsx // Good — numbered scale step
...
// Bad — hardcoded hex
...
// Bad — v1 semantic color names in a v2 component
...
``` ### Respect step ranges ```tsx // Good — step 3 for element background, step 11 for text // Bad — step 11 is a text step, not a background step ``` ### Do not mix v1 and v2 colors ```tsx // Bad — mixing v1 (txt-primary) and v2 (sand-3) in one component
...
// Good — all v2
...
``` ### Let the theme handle dark mode ```tsx // Bad — manual dark: overrides for v2 colors
...
// Good — just use the scale; dark values come from the theme scope
...
``` ### Solid backgrounds (steps 9–10) Steps 9 and 10 are designed for prominent, solid-color backgrounds (primary buttons, badges, banners). Most step 9 colors are designed for white foreground text. Exceptions: **sky**, **amber** are designed for dark foreground text on steps 9–10. ```tsx // Good — green solid button with white text // Good — amber badge with dark text (amber 9-10 are light/bright) Warning ``` # Typography (`text-1` … `text-9`) The type scale mirrors the color scale: numbered steps, not t-shirt sizes. Each `text-` utility carries its paired font-size, line-height, and letter-spacing. Theme file: [`packages/ui/src/v2/theme/typography.css`](../../packages/ui/src/v2/theme/typography.css) | Step | Size | Typical use | |------|------|-------------| | 1 | 12px | Fine print, captions, metadata | | 2 | 14px | Secondary / dense body, table cells | | 3 | 16px | Body default | | 4 | 18px | Lead paragraph, small headings | | 5 | 20px | Section heading | | 6 | 24px | Page heading | | 7 | 28px | Large heading | | 8 | 35px | Display | | 9 | 60px | Hero | Font family is **Inter Variable** (`font-sans`); mono is a system stack (`font-mono`). Weights: `font-light` (300), `font-normal` (400), `font-medium` (500), `font-bold` (700). ```tsx // Good — numbered type step + weight + color step

Measures

Description

// Bad — Tailwind default size (wiped in v2) or arbitrary value

Measures

Description

``` > Prefer the kit's `Text` / `Heading` / `Code` components over raw elements with these classes — they encode the step choice once. See [Typography: components over raw text elements](ui.md#typography-components-over-raw-text-elements). # Radius (`rounded-1` … `rounded-6`) Numbered radius scale (Radix "Medium" set). The static `rounded-none` / `rounded-full` utilities still work; the numeric ramp replaces Tailwind's `rounded-sm/md/lg`. Theme file: [`packages/ui/src/v2/theme/radius.css`](../../packages/ui/src/v2/theme/radius.css) | Step | Value | Typical use | |------|-------|-------------| | 1 | 3px | Subtle rounding (chips, small inputs) | | 2 | 4px | Inputs, small buttons | | 3 | 6px | Buttons, list items | | 4 | 8px | Cards, dialogs | | 5 | 12px | Large surfaces | | 6 | 16px | Hero panels, modals | ```tsx // Good — numbered radius
…
// Bad — Tailwind default radius (wiped) or arbitrary value
…
…
``` # Shadows (`shadow-1` … `shadow-6`, `inset-shadow-1` … `inset-shadow-3`) Drop-shadow elevation ramp, sand-tinted so it adapts to dark mode automatically (built from alpha tokens that flip light↔dark). Inset shadows live in the separate `inset-shadow-*` slot. Theme file: [`packages/ui/src/v2/theme/shadows.css`](../../packages/ui/src/v2/theme/shadows.css) | Step | Typical use | |------|-------------| | 1 | Hairline lift (resting cards) | | 2 | Raised cards, inputs | | 3 | Dropdowns, popovers | | 4 | Dialogs | | 5 | Large overlays | | 6 | Highest elevation (modals over modals) | ```tsx // Good — numbered elevation; dark mode is automatic
…
// Bad — Tailwind default shadow (wiped) or a manual dark: override
…
``` # Z-index (`z-1` … `z-6`) Stacking scale by **UI layer role**, not arbitrary elevation. Prefer the step that matches the element type; never invent ad-hoc values (`z-10`, `z-[999]`) in a v2 build. Theme file: [`packages/ui/src/v2/theme/z-index.css`](../../packages/ui/src/v2/theme/z-index.css) | Step | Value | Role | |------|-------|------| | 1 | 1 | Local stacking inside a component (media over a decorative backdrop) | | 2 | 10 | Sticky / fixed page chrome (top bars, sticky toolbars) | | 3 | 30 | Floating menus (select, dropdown, tooltip, popover) | | 4 | 40 | Overlay backdrops (dimmers under modals / drawers) | | 5 | 50 | Modals and drawers | | 6 | 60 | Toasts / global notifications (always on top) | Put the token on the **portaled root** that participates in the document stacking context (e.g. a menu `Positioner`), not only an inner popup. A local `z-1` in the page will paint above a portaled popup that has no z-index of its own. ```tsx // Good — local media above its own backdrop; menu sits in the popover layer
…logo…
… // Bad — Tailwind default / arbitrary z-index (wiped or discouraged in v2)
…
…
``` # Spacing (Tailwind native) Spacing is **not** tokenized — use Tailwind's native spacing scale (`p-4`, `gap-2`, `mt-6`, `size-8`). Do not invent a numbered spacing scale or use arbitrary pixel values where a native step fits. ```tsx // Good — native spacing scale
…
// Bad — arbitrary spacing where a native step exists
…
```