Naming a colorblue-500tells you what it is. Naming itbrandtells you what it does. Both have a place. The art is knowing which goes where.
Literal tokens (built-in)
Tailwind ships a palette: blue-500, slate-900, cyan-400. These are literal — they describe a specific hex value. Use them for one-off accents, dev work, scratch UIs.
Semantic tokens (yours)
Semantic tokens are the names you add: brand, danger, surface, text-muted. These describe role, not appearance. The exact color can change (e.g. brand color rebrand) without touching every component that uses it.
The two-layer pattern
Most mature design systems use literals to define and semantics to use:
@theme {
/* Literal palette */
--color-pink-500: #FF8FBE;
/* Semantic mapping */
--color-brand: var(--color-pink-500);
}
This way you can swap --color-brand from pink-500 to violet-500 in one line and every bg-brand across the app updates.
The cwkPippa palette
cwkPippa's index.css uses semantic tokens almost exclusively in components (bg-bg, text-fg, text-brand) and pulls them from a small literal layer. The literal layer is the one place that changes when Dad picks a new accent color.
What makes a good semantic name
- Role-based:
danger,success,warning,infobeatred,green,yellow,blue. - Surface-based:
bg,bg-elevated,bg-cardbeatgray-950,gray-900,gray-850. - Hierarchy-aware:
text,text-muted,text-subtlefor primary/secondary/tertiary copy.
blue can't become red without breaking semantics. A token called brand can be any hue. Pick names that allow the color to change without changing the meaning.