Design Tokens
DelightStack uses CSS custom properties (design tokens) for all visual styling. Components reference these tokens internally, so customizing them changes the look of every component at once.
Two tiers
Section titled “Two tiers”The token system has two layers:
- Primitives — the raw OKLCH color ramp (
--color-50…--color-950) and a few internal scales, all derived from a single brand seed. These are an implementation detail; don’t reference them directly. - Semantic tokens — the public API documented below (
--color-bg,--color-text-muted,--radius-md, …). Components reference only these. They’re defined in terms of the primitives, so changing the brand seed (or any semantic token) re-themes everything.
The fastest way to get the tokens is to import the package — it ships them all with light/dark values built in:
import '@delightstack/styles';To re-theme, override the brand seed (or any individual semantic token) in your own CSS. --color-primary is the only required seed — --color-secondary (accent controls) and --color-neutral (the background/text ramp tint) derive from it automatically:
:root { --color-primary: #2563eb; /* the only required seed — re-derives everything */ --color-neutral: #6b7280; /* optional: decouple the surface tint from the brand */ --color-action: light-dark(#2563eb, #3b82f6); /* or override a token outright */ --radius-md: 0.5rem;}Try it live
Section titled “Try it live”Customize the seeds below and the whole docs site re-themes in place — every component demo on every page runs on the same tokens. Your choices persist in this browser (the palette button in the navbar opens the same editor), and the generated CSS is ready to paste into your own app.
Theme
One color themes everything — the rest of the palette derives from it. Pick a primary and the whole site re-themes live. Saved in this browser.
No overrides yet — the pickers show the current defaults.
Colors
Section titled “Colors”Background & surface
Section titled “Background & surface”| Token | Role |
|---|---|
--color-bg | Page background |
--color-bg-muted | Recessed areas — input wells, code blocks, control tracks |
--color-bg-active | Hover / selected surface |
--color-bg-disabled | Disabled element backgrounds |
--color-surface | Elevated surfaces — cards, popovers, modals |
| Token | Role |
|---|---|
--color-text | Primary text |
--color-text-muted | Secondary / helper text |
--color-text-active | Active / pressed text |
--color-text-disabled | Disabled text |
Border
Section titled “Border”| Token | Role |
|---|---|
--color-border | Default borders and dividers |
--color-border-active | Focused / active borders |
--color-border-disabled | Disabled borders |
Action (primary interactive)
Section titled “Action (primary interactive)”Define only the base color — the disabled, active, and text states are derived automatically with relative color syntax.
:root { --color-action: light-dark(#005640, #34d399); --color-action-active: /* derived */ ; --color-action-disabled: /* derived */ ; --color-action-text: /* tinted off-white on the action color */ ; --color-action-text-active: /* derived */ ; --color-action-text-disabled: /* derived */ ;}Accent (secondary interactive)
Section titled “Accent (secondary interactive)”Same six tokens as action, seeded from --color-secondary: --color-accent, --color-accent-active, --color-accent-disabled, --color-accent-text, --color-accent-text-active, --color-accent-text-disabled.
Feedback
Section titled “Feedback”--color-error and --color-success each carry a full state set (-active, -disabled, -text, -text-active, -text-disabled). --color-warning is a single color. All three are fixed hues — red, green, and amber carry meaning, so they don’t derive from the brand seed. There is no separate “info” color: informational UI (info callouts, toasts) uses the brand’s --color-action. Each feedback color also has a soft tinted background for callouts and alerts:
:root { --color-error: light-dark(#ef6262, #b04343); --color-success: light-dark(#00b7a1, #2c8f83); --color-warning: light-dark(#e89c08, #e0a020);
/* Soft backgrounds, mixed over the page background */ --color-error-bg: /* … */ ; --color-success-bg: /* … */ ; --color-warning-bg: /* … */ ; --color-action-bg: /* … (used by info callouts/toasts) */ ;}Overlay
Section titled “Overlay”| Token | Role |
|---|---|
--color-backdrop | Scrim behind modals / sheets |
Border radius
Section titled “Border radius”| Token | Value | Typical usage |
|---|---|---|
--radius-none | 0 | Sharp corners |
--radius-sm | 2px | Inputs, small elements |
--radius-md | 5px | Buttons, default elements |
--radius-lg | 10px | Cards, panels |
--radius-xl | 20px | Large containers |
--radius-2xl | 30px | Modals, sheets |
--radius-3xl | 60px | Prominent / hero elements |
--radius-full | 1e5px | Pills, avatars |
Z-index layers
Section titled “Z-index layers”| Token | Value | Components |
|---|---|---|
--layer-base | 0 | Default content |
--layer-dropdown | 100 | Select dropdowns, menus |
--layer-sticky | 200 | Sticky headers |
--layer-drawer | 300 | Drawer, BottomSheet |
--layer-modal | 400 | Modal, Alert, lightbox |
--layer-popover | 500 | Popover, CommandPalette |
--layer-toast | 600 | Toast notifications |
--layer-tooltip | 700 | Tooltips |
--layer-max | — | Force-on-top escape hatch |
Typography
Section titled “Typography”Families
Section titled “Families”:root { --font-sans: system-ui, -apple-system, Segoe UI, Roboto, sans-serif; --font-serif: 'Playfair Display', ui-serif, serif; --font-mono: ui-monospace, SF Mono, Menlo, monospace;}The --text-* scale is the public typography API. (Internally it aliases a numbered --font-size-* scale that powers the component size prop — see Size System.)
| Token | Value |
|---|---|
--text-xs | 0.65rem |
--text-sm | 0.815rem |
--text-base | 1rem |
--text-lg | 1.1rem |
--text-xl | 1.25rem |
--text-2xl | 1.5rem |
--text-3xl | 2rem |
--text-4xl | 2.5rem |
--text-5xl | 3rem |
--text-6xl | 3.5rem |
Fluid variants (--text-fluid-0 … --text-fluid-3) scale with the viewport via clamp().
Weights & spacing
Section titled “Weights & spacing”:root { --font-weight-normal: 400; --font-weight-medium: 500; --font-weight-semibold: 600; --font-weight-bold: 700;
--leading-tight: 1.25; --leading-normal: 1.5; --leading-relaxed: 1.75;
--tracking-tight: -0.05em; --tracking-normal: 0; --tracking-wide: 0.05em; --tracking-wider: 0.15em;}Spacing
Section titled “Spacing”A numeric scale from --space-1 (0.25rem) to --space-15 (30rem), plus fluid variants --space-fluid-1 … --space-fluid-5 that scale with the viewport.
Shadows & elevation
Section titled “Shadows & elevation”In light mode, components use traditional drop shadows; in dark mode the shadows fade out (dark shadows are invisible on dark surfaces), so elevation reads through surface color and borders instead.
| Token | Usage |
|---|---|
--shadow-sm | Subtle lift |
--shadow-md | Buttons, cards |
--shadow-lg | Popovers, dropdowns |
--shadow-xl | Modals |
--shadow-2xl | Prominent overlays |
Transitions
Section titled “Transitions”:root { --duration-fast: 100ms; --duration-normal: 200ms; --duration-slow: 300ms; --duration-slower: 500ms;
--ease-default: cubic-bezier(0.76, 0, 0.24, 1); --ease-in: cubic-bezier(0.5, 0, 0.75, 0); --ease-out: cubic-bezier(0.33, 1, 0.68, 1); --ease-in-out: cubic-bezier(0.76, 0, 0.24, 1); --ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1); /* playful overshoot */}Skeleton
Section titled “Skeleton”Every component’s skeleton loading mode is driven by one shared set of tokens, so all
skeletons in an app shimmer in the same color and rhythm (and can be re-themed in one place):
:root { /* Placeholder fill — translucent text color so it sits on any surface */ --skeleton-bg: color-mix(in oklch, var(--color-text) 10%, transparent); /* The brighter beam swept across each placeholder shape */ --skeleton-sheen: color-mix(in oklch, var(--color-text) 12%, transparent); /* One shimmer cycle: the beam sweeps ~55% of the cycle, then rests */ --skeleton-duration: 2.4s;}The sweep itself is the global delight-skeleton-shimmer keyframe (each component declares
it, so it works even without the global stylesheet). Components with several placeholder
shapes stagger the sweep per item, which makes the shimmer travel through the skeleton as a
wave.
Size System
Section titled “Size System”Components use a numeric size prop where '1' is the default. The prop maps to font-size internally (via the numbered --font-size-* scale), and the rest of the component scales with em units:
'0000' → '000' → '00' → '0' → '1' (default) → '2' → '3' → '4' → '5' → '6'Not every component uses the full range — each defines the relevant subset:
<Button size="0">Small</Button><Button>Normal (default)</Button><Button size="3">Large</Button>Form controls (Input, Select, the form Button heights, and the smaller controls) use a
'0'–'3' subset that also drives a shared height — see Control sizing.
Control sizing
Section titled “Control sizing”Form controls share one canonical height system so a row of them lines up. The size
prop picks a font from the --control-font-* scale, and the control’s height is then
font × ratio. The density modifiers (dense / comfortable) swap the ratio. One scale →
a default row is the same height, and a dense row is the same height, with no per-component
hardcoding.
:root { /* rem so controls scale with the root font-size; even-px fonts × 0.5-step ratios keep every size on a whole-pixel height */ --control-font-0: 0.875rem; /* 14px → 42px tall */ --control-font-1: 1rem; /* 16px → 48px tall (default) */ --control-font-2: 1.125rem; /* 18px → 54px tall */ --control-font-3: 1.25rem; /* 20px → 60px tall */
--control-height-ratio: 3; /* default → 48px @ size 1 */ --control-height-ratio-dense: 2.5; /* dense → 40px @ size 1 */ --control-height-ratio-comfortable: 3.5; /* comfortable → 56px @ size 1 */
--control-pad-x: 1em; /* inline padding */ --control-pad-x-dense: 0.75em; --control-pad-x-comfortable: 1.25em;}Input, Select and Button (including standalone icon buttons) snap to these heights:
size | dense | default | comfortable |
|---|---|---|---|
| 0 | 35px | 42px | 49px |
| 1 | 40px | 48px | 56px |
| 2 | 45px | 54px | 63px |
| 3 | 50px | 60px | 70px |
So a row of controls at the same size (and density) is the same height:
<div style="display: flex; gap: 0.75rem; align-items: start;"> <Input label="Search" /> <Select label="Sort" {options} /> <Button>Go</Button></div>The smaller controls (Checkbox, Radio, Toggle, Range, Rating) read the same
--control-font-* scale so size means the same text scale everywhere; they keep their
natural glyph height and center within a control-height row (align-items: center).
Breakpoints
Section titled “Breakpoints”Used sparingly for page-level layout (components prefer container queries):
:root { --breakpoint-sm: 640px; --breakpoint-md: 768px; --breakpoint-lg: 1024px; --breakpoint-xl: 1280px;}