Tokens + Primitives + Recipes
Building a Design System
Combine @theme tokens, primitive components, and variant recipes into a coherent, multi-brand design system.
What you'll learn
- Define semantic tokens, not just raw colors
- Build primitives with cva
- Document and share a theme as a package
A design system in Tailwind v4 is three layers: semantic tokens defined in @theme, primitive components that consume them, and variant recipes built with cva.
Semantic Tokens
Do not scatter blue-500 everywhere. Define meaning-named tokens so a rebrand is one file:
@import "tailwindcss";
@theme {
--color-surface: oklch(1 0 0);
--color-fg: oklch(0.21 0.02 256);
--color-accent: oklch(0.62 0.19 256);
} These generate bg-surface, text-fg, bg-accent, and so on — utilities that describe role, not appearance.
Primitives Consume Tokens
Primitive components reference only semantic tokens, never raw palette values:
const card = cva('rounded-lg bg-surface text-fg p-6 shadow-sm');
export const Card = (props) => <div className={card()} {...props} />; Multi-Brand by Token Override
Because primitives only read tokens, a second brand is just a scoped override of those custom properties:
[data-brand="acme"] {
--color-accent: oklch(0.55 0.21 28);
} Ship It as a Package
Bundle theme.css plus the primitive components into an internal package. Every app imports the same tokens and recipes, so visual consistency is enforced by the dependency, not by code review.