Source:
ocean/docs/adr/0025-design-system-pipeline-tailwind4-shadcn.md| ✏️ Edit on GitHub
ADR-0025: Token-driven Design System with Tailwind 4 and shadcn
Date: 2025-08-14
Status
Accepted
Context
We need a maintainable, future-proof design system that:
- Retains shadcn component styling and semantics
- Uses Tailwind 4
@themewith CSS variables - Supports multiple brandable color palettes (light/dark)
- Syncs with Figma tokens and avoids runtime CSS mutation
Previously, color theme switching mutated CSS variables at runtime in React, which is fragile and diverges from Tailwind 4 best practices. Our token pipeline also mixed responsibilities between generated and hand-authored files.
Decision
- Use Style Dictionary (SD) as the single source-of-truth compiler for tokens.
- Maintain base, non-palette tokens in
design-tokens/base-tokens.json. - Maintain color palettes in
design-tokens/palettes.jsonwith light/dark per palette. - Generate:
src/styles/tokens.generated.css(base tokens)src/styles/color-themes.generated.css(per-palette CSS keyed bydata-color-themewith.darkoverrides)
- Tailwind 4
@themeinsrc/styles.cssmaps component semantic colors (e.g.,bg-background) to CSS vars. shadcn classes remain unchanged. - Theme mode (light/dark) continues via
next-themes(adds.darkclass). Palette selection is reflected viadata-color-themeon<html>. - Pre-hydration script sets
data-color-themeto remove flash of wrong palette.
Alternatives Considered
- Runtime mutation of CSS variables in React hooks: rejected for brittleness and poor integration with Tailwind 4.
- Keeping a separate design system package (Odyssey): rejected to simplify maintenance and prevent drift with shadcn.
- Using raw Figma JSON at runtime: rejected; we prefer a build-time SD pipeline.
Consequences
- Pros:
- Single pipeline from tokens to CSS vars; easy to evolve palettes and brands
- shadcn semantics preserved
- Zero runtime CSS mutation; better performance and predictability
- Pre-hydration eliminates palette flash
- Cons:
- Requires developers to edit tokens and run the build (automated in prebuild/predev)
Implementation Notes
- Files added/updated:
design-tokens/palettes.json: palette sourcetools/convert-palettes-to-tokens.js: ensures SD-friendly token shapestyle-dictionary.config.js: emitstokens.generated.cssandcolor-themes.generated.csssrc/styles.css: imports generated palettes and maps to Tailwind 4@themesrc/hooks/use-color-theme.ts: setsdata-color-themeinstead of mutating CSSindex.html: pre-hydration script fordata-color-theme
- Scripts:
pnpm run tokens:buildbuilds tokens and palettes (predev/prebuild)pnpm run design:validateconfirms generated palettes match config
Validation
We added tools/validate-design.js to compare src/config/themes.ts with src/styles/color-themes.generated.css. Current run shows no mismatches, so the visual design remains unchanged.