Semantic tokens, not hex
Style surfaces with tokens like bg-card, never hex or palette classes.
shadcn/uiui.shadcn.comTheming
From shadcn: Theming, and palette classes as an anti-pattern.
Edit a token, see who follows
Pick a token, then drag its sliders.
HardcodedBad
Invite teammate
They get access to this workspace.
3 seats left
bg-neutral-900border-neutral-800bg-[#1447e6]TokensGood
Invite teammate
They get access to this workspace.
3 seats left
bg-cardborder-borderbg-primaryBoth panes match.
/* globals.css */
@theme inline {
--color-background: oklch(0.145 0 0);
--color-card: oklch(0.205 0 0);
--color-primary: oklch(0.488 0.243 264.376);
--color-ring: oklch(0.488 0.243 264.376);
--color-border: oklch(0.269 0 0);
--color-input: oklch(0.269 0 0);
}
<Card className="bg-card text-card-foreground border-border">
<Button className="bg-primary text-primary-foreground" />
</Card>
Swap the base palette
Pick a base color and a mode.
Hex + palette classesBad
Overview
Revenue$48.2k
Open12
Overdue3
Acme Inc.Paid
GlobexDue
bg-neutral-950text-neutral-400bg-blue-700Semantic tokensGood
Overview
Revenue$48.2k
Open12
Overdue3
Acme Inc.Paid
GlobexDue
bg-backgroundtext-muted-foregroundbg-primaryneutral, dark
/* components.json: "baseColor": "neutral" */
.dark {
--background: oklch(0.145 0 0);
--card: oklch(0.205 0 0);
--muted-foreground: oklch(0.708 0 0);
--border: oklch(0.269 0 0);
}
@theme inline {
--color-background: var(--background);
--color-card: var(--card);
--color-muted-foreground: var(--muted-foreground);
--color-border: var(--border);
}
Find the drift
Hover or tap a part, then switch to light.
Settings card3 of 8 hardcoded
Workspace
Shown on invoices and emails.
-Dark mode hides the drift.
<Card className="bg-card border-border">
<CardTitle className="text-card-foreground">Workspace</CardTitle>
<CardDescription className="text-muted-foreground">
Shown on invoices and emails.
</CardDescription>
<Label className="text-foreground">Name</Label>
<Input className="border-input focus-visible:ring-ring" />
<Button>Save</Button> {/* bg-primary text-primary-foreground */}
<Button variant="secondary">Cancel</Button>
</Card>
Why it matters, pros and cons
Why it matters
A token names a role, not a color; change it once and every surface follows.
Pros
- Dark mode is one CSS block.
- Surface and foreground stay paired.
- Hex on a surface is greppable.
Cons
bg-cardhides the color; check devtools.- Token sprawl: add tokens for roles, not elements.
- Brand marks and data-viz hues should not re-theme.