Aller au contenu principal
All articles
Design

Tailwind v4 design system: tokens, primitives, composites

How to build a coherent design system with Tailwind v4: CSS-first tokens, typed primitives, accessible composites. The VALRY LABS experience report.

VALRY LABS Design TeamApril 2, 202511 min read

Why Tailwind v4 changes the game

Tailwind v4 pushes tokens towards native CSS via `@theme`. Gone is the verbose `tailwind.config.js` — in come real CSS variables, usable in both JS and CSS, overridable by media query, theme, or context.

Immediate benefit: a dark mode that no longer requires `dark:` on every class. You redefine the CSS variables inside `:root[data-theme="dark"]` and the whole design system switches cleanly.

Performance: Tailwind v4 generates less CSS than v3 for the same design. On the VALRY LABS codebase, the final CSS went from 32 kB to 18 kB gzip (-44%).

Tokens: the source of truth

Three-layer structure: raw tokens (base colours, scales, durations), semantic tokens (background, border, and accent colours per context), component tokens (variant X of the button).

Raw tokens are never used directly inside components. Everything goes through the semantic layer. That lets you change the base palette without touching a single component.

Semantic naming convention: `--color-bg-primary`, `--color-fg-primary`, `--color-border-strong`, `--color-accent`. Readable, unsurprising, dark-mode ready.

Storage: tokens in CSS (`globals.css`), mirrored in TypeScript (`tokens.ts`) for JS usage (canvas, dynamic charts). Both files are generated from a single `tokens.json` source via Style Dictionary.

Primitives: the atomic layer

Primitives = the reusable base bricks: Button, Card, Container, Heading, Badge, Overline. No business logic, only stylistic variants.

We use shadcn/ui as the base (Radix + Tailwind, copy-paste, no runtime dependency) then customise. It avoids reinventing the wheel on complex components (Dialog, Popover, Combobox) while keeping ownership of the code.

Rule: every primitive exposes its variants via `cva` (class-variance-authority). No ternaries in `className`, no concatenated strings. Composition is typed and IDE-toolable.

Native accessibility: Radix UI guarantees keyboard behaviour, ARIA, focus traps, and more. We never hand-roll a Combobox or a Dialog — we dress Radix.

Composites: the business layer

Composites = assemblies of primitives into functional units: `ServiceCard`, `TestimonialCard`, `HeroSection`, `CTASection`. This is where the brand's signature design lives.

A composite never reinvents a button: it consumes `<Button>` and passes the right `variant` / `size`. A composite's API is intentionally narrow — no primitive leakage.

On VALRY LABS, composites live in `components/sections/` (full page blocks) or `components/cards/` (business cards). Each has its Storybook demo.

Accessibility: non-negotiable

WCAG 2.2 AA is the minimum target. Contrasts validated by `@axe-core/playwright` in CI. No merge if a critical violation is detected.

Components: visible focus (`focus-visible:ring-2`), touch targets ≥ 44×44 px, `aria-label` on icon-only buttons, `aria-describedby` for form errors.

Animations: everything respects `prefers-reduced-motion`. Motion components have an instant fallback. Beauty never justifies exclusion.

Regular audits: Lighthouse a11y ≥ 95 mandatory, manual NVDA/VoiceOver review for every major feature.

Key takeaways

Key points.

  • Tailwind v4 pushes tokens towards native CSS via `@theme` — simpler dark mode, -44% CSS.
  • Tokens in 3 layers: raw → semantic → component. Never a raw token inside a component.
  • Primitives: shadcn/ui (Radix) for complex components, `cva` for variants.
  • Composites = business assemblies, never reinvented primitives.
  • WCAG 2.2 AA: axe in CI, Lighthouse ≥ 95, manual NVDA/VoiceOver.
TailwindDesign SystemTokensAccessibility