Theming
Semantic tokens in shadcn's canonical vocabulary, on two orthogonal seams — a swappable ground (surfaces) and a brand palette (accent) — that switch at runtime, independently of each other and of light/dark.
How it works
Two tiers: raw variables on :root and .dark, aliased into Tailwind's --color-* namespace.
Because every utility resolves through a CSS variable, the whole surface re-colors at runtime — for the light/dark toggle and the palette switch — without any component branching on the theme. The token names follow shadcn's vocabulary (background, card, primary, muted, accent, destructive, …), so canonical shadcn components drop in unmodified.
Ground
The ground is the near-neutral surface set — background, card, popover, muted, border. Three ship: paper (the default cool grey), void (indigo navy), and slate (steel). Selected with data-ground on <html>. Try it — the surfaces re-color while the accent stays put.
Card surface
muted-foreground on the card
Ground and palette are independent seams, so any ground composes with any accent. The default ground is paper; alternates are data-ground="void" / "slate", or driven from useGround. Void and slate are lifted from real apps; every ground is parity-tested against the stylesheet.
Palette
A swappable palette supplies primary, primary-foreground, ring, and a decorative gradient — independent of the ground. Try it — the accent re-colors.
The base is Eucalyptus. Palettes are selected with data-palette="…" on <html> — set it once for an application-wide default, or drive it from usePalette for a runtime switcher like this one. Every palette is parity-tested against the stylesheet.
Color
The semantic tokens, resolved in the current theme.
Type scale
Radius
Motion
Shared easing and duration constants. Components import these; a cubic-bezier is never retyped.
| EASE.outExpo | cubic-bezier(0.32, 0.72, 0, 1) | [0.32, 0.72, 0, 1] |
| EASE.out | cubic-bezier(0.16, 1, 0.3, 1) | [0.16, 1, 0.3, 1] |
| EASE.inOut | cubic-bezier(0.77, 0, 0.175, 1) | [0.77, 0, 0.175, 1] |
| DURATION.fast | 120ms | |
| DURATION.base | 180ms | |
| DURATION.slow | 260ms | |
Scrollbars
Overlay scrollbars are the default: a thin, token-colored thumb over a transparent track that reveals on intent — near-invisible at rest, fading in while a scroll area is hovered. The gutter is reserved so bars never shift layout.
default — hover to reveal
Scrollable line 1 — the thumb fades in on hover.
Scrollable line 2 — the thumb fades in on hover.
Scrollable line 3 — the thumb fades in on hover.
Scrollable line 4 — the thumb fades in on hover.
Scrollable line 5 — the thumb fades in on hover.
Scrollable line 6 — the thumb fades in on hover.
Scrollable line 7 — the thumb fades in on hover.
Scrollable line 8 — the thumb fades in on hover.
Scrollable line 9 — the thumb fades in on hover.
Scrollable line 10 — the thumb fades in on hover.
Scrollable line 11 — the thumb fades in on hover.
Scrollable line 12 — the thumb fades in on hover.
Scrollable line 13 — the thumb fades in on hover.
Scrollable line 14 — the thumb fades in on hover.
Scrollable line 15 — the thumb fades in on hover.
Scrollable line 16 — the thumb fades in on hover.
Scrollable line 17 — the thumb fades in on hover.
Scrollable line 18 — the thumb fades in on hover.
.scrollbar-none — chrome hidden
Opt out of the chrome with .scrollbar-none or data-scroll="hidden" — for still, one-viewport surfaces where scrolling is exceptional, never for documents.