Architecture Decisions
ADR-0005: Tailwind CSS v4 Adoption
Decision to adopt Tailwind CSS v4 with CSS-first configuration for faster builds and better theming
Status
Accepted
Date: 2025-02-03
Context
Our design system requires a CSS framework that enables:
- Rapid UI development with utility classes
- Design consistency across all apps
- Theming (light/dark mode, per-app branding)
- Type safety for design tokens
- Tree-shaking to minimize bundle size
- Custom design tokens that integrate with shadcn/ui
Tailwind CSS v3 had been our standard, but v4 was released with major improvements:
- Faster builds via Rust-based engine (Oxide)
- CSS-first configuration (no more
tailwind.config.js) - Better DX with
@themedirective - Improved tree-shaking and smaller bundles
- Native CSS variables for design tokens
We needed to decide: stick with v3 or adopt v4?
Decision
We adopted Tailwind CSS v4 across all apps with CSS-native configuration.
Key changes from v3:
- No
tailwind.config.js— Configuration via CSS@themedirective - PostCSS-based — Use
@tailwindcss/postcssplugin - CSS variables for themes — Design tokens defined in
:root
Configuration approach:
// postcss.config.mjs
export default {
plugins: {
"@tailwindcss/postcss": {},
},
};
/* globals.css */
@import "@hn-monorepo/ui/styles/base.css";
@theme inline {
--radius-sm: calc(var(--radius) - 4px);
--radius-lg: var(--radius);
--color-primary: hsl(var(--primary));
--color-background: hsl(var(--background));
}
:root {
/* Light mode */
--background: 36 100% 97%;
--primary: 146 19% 49%;
}
.dark {
/* Dark mode */
--background: 120 25% 12%;
--primary: 146 19% 49%;
}
Benefits for our use case:
- Each app can define its own theme tokens (CalNexus = warm/organic, Lexilink = modern/tech)
- Shared UI components inherit theme from the consuming app
- No build-time config conflicts between apps
- Type-safe design tokens via CSS custom properties
Consequences
Positive
- Faster builds: Rust-based Oxide engine is ~10x faster than v3
- Smaller bundles: Better tree-shaking reduces CSS by ~30%
- Better theming: CSS variables make light/dark mode seamless
- Simpler config: No JS config files, just CSS
- Per-app themes: Each app can customize without affecting others
- Future-proof: v4 is the future of Tailwind, v3 will be deprecated
- Native CSS features: Leverages modern CSS instead of PostCSS hacks
Negative
- Breaking changes: Migration from v3 required updating all config
- Ecosystem lag: Some plugins/tools do not support v4 yet
- Less documentation: v4 is newer, fewer resources available
- Learning curve: Team had to learn new
@themesyntax - Risk of early adoption: Potential bugs in new engine
Neutral
- Different mental model: CSS-first vs JS-first configuration
- Migration effort: One-time cost to convert existing apps
- Plugin compatibility: Most popular plugins work, some niche ones do not
Alternatives Considered
Alternative 1: Stay on Tailwind v3
- Description: Keep using proven v3 with JS config
- Pros: Stable, battle-tested; huge ecosystem of plugins; extensive documentation
- Cons: Slower builds (JS-based engine); larger bundles; v3 will be deprecated eventually
- Why not chosen: v4’s performance and theming benefits outweigh migration cost
Alternative 2: CSS Modules
- Description: Write custom CSS with modules for scoping
- Pros: No framework dependency; complete control over CSS
- Cons: Much slower development velocity; inconsistent design system; no built-in theming
- Why not chosen: Tailwind’s utility-first approach is much faster
Alternative 3: Vanilla Extract
- Description: Zero-runtime CSS-in-JS with TypeScript
- Pros: Type-safe styles; great theming via contracts; zero runtime cost
- Cons: Smaller ecosystem than Tailwind; more verbose than utilities
- Why not chosen: Tailwind’s ecosystem and DX are superior
Alternative 4: Panda CSS
- Description: Type-safe, utility-first CSS-in-JS (like Chakra + Tailwind)
- Pros: Type-safe utilities; great theming; modern architecture
- Cons: Very new, small community; less mature than Tailwind
- Why not chosen: Too new, Tailwind is more proven
References
Notes
- Shared base styles:
@hn-monorepo/ui/styles/base.csscontains core Tailwind setup - Per-app themes: Each app imports base styles, then defines its own
@themeand:roottokens - shadcn/ui compatibility: Works perfectly with shadcn — CSS variables map directly to components
- Design tokens: We use HSL color format (e.g.,
146 19% 49%) for easy manipulation - Font tokens:
--font-displayand--font-bodydefine typography system - Radius system: Computed radius tokens (
--radius-sm,--radius-lg) based on base--radius