Docs
Theming
The token layers, light and dark, re-branding, radius roles and the concentric formula.
Every colour, shadow, radius and duration in Eased is a CSS variable. Components read only semantic names, so you re-theme the whole library by restating a few variables. You don't edit any component.
How the tokens are layered
| Layer | Example | What it's for |
|---|---|---|
| Raw ramps | --eased-neutral-1 … --eased-neutral-12, --eased-brand-1 … --eased-brand-12 | Twelve-step OKLCH scales, authored separately for light and dark. Restate these to re-brand. |
| Semantic aliases | --eased-surface, --eased-line-subtle, --eased-text-muted, --eased-brand | What a colour is for. Components never read a raw step. |
| Tailwind utilities | bg-surface, border-line-subtle, text-text-muted, bg-brand | Generated by @theme inline from the aliases, so they follow the theme at runtime. |
Each layer reads only the layer below it. The neutral ramp follows the Radix step roles: 1 app background, 2 subtle background, 3 component background, 4 hover, 5 pressed, 6 subtle border, 7 border, 8 strong border, 9 solid, 10 solid hover, 11 low-contrast text, 12 text.
Semantic tokens
| Utility | Use it for |
|---|---|
bg-bg | The page canvas |
bg-surface, bg-surface-sunken, bg-surface-raised | Cards; wells and tracks; popovers and dialogs |
bg-surface-hover, bg-surface-active | Hover fill; pressed or selected fill |
border-line-subtle, border-line, border-line-strong | Dividers and card edges; interactive edges; inputs at rest |
text-text, text-text-secondary, text-text-muted, text-text-disabled | Headings and labels; descriptions; metadata; disabled text |
bg-brand, text-text-on-brand, text-brand-text, bg-brand-subtle | The one primary action; text on it; brand-coloured text; a brand tint |
bg-success-subtle + text-success-text (and warning, danger, info) | Status badges. Use solid fills only on buttons and progress. |
shadow-resting, shadow-raised, shadow-floating, shadow-overlay | Resting cards; hover lift; popovers and menus; dialogs |
inset-shadow-well, inset-shadow-highlight | Sunken inputs and tracks; the top highlight on primary buttons |
All colours are OKLCH, so steps are evenly spaced in perceived lightness, and a hue change keeps contrast.
Switch to dark
<html class="dark">Light theme is defined on :root. Dark theme is defined on .dark (shadcn's convention) and on [data-theme="dark"], so either one works on any ancestor.
Dark is authored, not inverted. Surfaces get lighter as they rise: surface is lighter than bg, and surface-raised is lighter again. Borders sit above their surface. Shadows carry about four times the alpha plus a faint white ring, so a raised card still reads on a dark canvas.
Change the brand colour
:root { --eased-brand-3: oklch(95% 0.03 265); --eased-brand-9: oklch(52% 0.2 265); --eased-brand-10: oklch(47% 0.2 265); --eased-brand-11: oklch(45% 0.16 265); --eased-text-on-brand: oklch(98% 0.01 265);}.dark { --eased-brand-3: oklch(30% 0.06 265); --eased-brand-9: oklch(72% 0.15 265); --eased-brand-10: oklch(78% 0.13 265); --eased-brand-11: oklch(80% 0.11 265); --eased-text-on-brand: oklch(18% 0.03 265);}- Add the brand steps to your global CSS, after the Eased block.
- Set your own values for both
:rootand.dark.
Components read steps 9 (fill), 10 (fill hover), 11 (text-safe) and 3 (tint) most. The default brand is warm ink in light and cream in dark.
To change the neutrals' tint, set --eased-neutral-hue. The default is 80, a warm tint; around 250 cools them toward blue. The focus ring and selection colour derive from the brand and neutral ramps, so they follow your change.
Radius roles
| Utility | Value | Use it for |
|---|---|---|
rounded-control-xs | 6px | Chips and badges up to 24px tall |
rounded-control-sm | 8px | Controls 28 to 32px tall |
rounded-control | 10px | Controls 36 to 40px tall |
rounded-panel | 16px | Cards, popovers, menus and toasts |
rounded-surface | 24px | Dialogs, sheets and full-bleed media |
rounded-pill | 9999px | Chips, switches and avatars only. Never a card. |
Components never use a literal radius; they use these roles. To change a role everywhere, restate its variable once, for example --eased-radius-panel: 12px.
Nest rounded boxes
<div className="rounded-panel border p-1.5 [--pad:6px] [--radius-outer:var(--eased-radius-panel)]"> <img className="radius-nested" src="/cover.jpg" alt="" /></div>When one rounded box sits inside another, the inner radius must equal the outer radius minus the space between them. Otherwise the corners look pinched:
inner radius = outer radius − padding − border width- On the parent, set
--radius-outerand--pad. - On the child, add the
radius-nestedutility.
The child computes its own radius from the two variables, clamped at zero. In the example, the card is 16px with 6px of padding and a 1px border, so the image gets 9px. If you change the card's radius, the image follows.
Every Eased component with a padded shell (Demo Frame, Segmented, Toggle Group, Popover, Hover Card, Composer and Concentric Frame) sets both variables on its root. Anything you put inside gets the right curve without a prop.
If the result is under 2px, raise the outer radius, or clip the child with overflow: clip instead of rounding it.
Control heights
| Utility | Height |
|---|---|
h-control-xs | 24px |
h-control-sm | 32px |
h-control-md | 36px |
h-control-lg | 40px |
h-control-xl | 48px |
Controls share these five heights, so everything in a row lines up. To give a smaller control a 44 by 44px tap target without changing how it looks, add the hit-area utility.