Skip to main content

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 @theme with 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.json with light/dark per palette.
  • Generate:
    • src/styles/tokens.generated.css (base tokens)
    • src/styles/color-themes.generated.css (per-palette CSS keyed by data-color-theme with .dark overrides)
  • Tailwind 4 @theme in src/styles.css maps component semantic colors (e.g., bg-background) to CSS vars. shadcn classes remain unchanged.
  • Theme mode (light/dark) continues via next-themes (adds .dark class). Palette selection is reflected via data-color-theme on <html>.
  • Pre-hydration script sets data-color-theme to 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 source
    • tools/convert-palettes-to-tokens.js: ensures SD-friendly token shape
    • style-dictionary.config.js: emits tokens.generated.css and color-themes.generated.css
    • src/styles.css: imports generated palettes and maps to Tailwind 4 @theme
    • src/hooks/use-color-theme.ts: sets data-color-theme instead of mutating CSS
    • index.html: pre-hydration script for data-color-theme
  • Scripts:
    • pnpm run tokens:build builds tokens and palettes (predev/prebuild)
    • pnpm run design:validate confirms 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.