Overview
Pick Tailwind for any team with a shared design system, a need to iterate fast on layout, and the discipline to set up the config (theme tokens, shadcn, Prettier plugin). Pick CSS Modules when each component genuinely owns a bespoke visual treatment, the design intentionally departs from utility-first thinking, or the team is small enough that style drift is not a real concern. The Tailwind bundle is small because it generates only what you use; the CSS Modules bundle is small because nothing is global. Both win on size; they lose to different team failure modes. See tailwind for the Tailwind rule set.
When Tailwind wins
Tailwind is the right pick for product teams larger than three, design systems with tokens, and any codebase that needs visual consistency across hundreds of components.
- Class-based composition keeps style decisions in the component file; no jumping between
.tsxand.module.cssto read a layout. - Design tokens live in
tailwind.config.ts(colors, spacing, radii); one change updates every component. - The Prettier plugin sorts classes deterministically; code review stops arguing about class order.
- Pairs natively with shadcn; the component kit and Tailwind share the same token vocabulary.
- Bundle size scales with utilities actually used, not utilities declared; a real app ships 10 to 30 KB of CSS.
When CSS Modules win
CSS Modules are the right pick when component styles are genuinely unique, the team values style locality over utility composition, or the design language resists tokenization.
- Each
.module.csscolocates with its component; styles cannot leak globally. - Preserves the full CSS spec (cascade, nesting, container queries) without Tailwind’s plugin layer; see css-cascade-layers and css-container-queries.
- Easier for designers who write CSS to contribute; no utility vocabulary to learn.
- Works without a config file; one-file portability matters for shared components.
- Best for marketing sites with bespoke art direction per page, where utility classes fight the design instead of expressing it.
Trade-offs at a glance
| Dimension | Tailwind | CSS Modules |
|---|---|---|
| Where styles live | Inline in JSX as classes | Sibling .module.css |
| Design tokens | First-class in config | Hand-rolled CSS custom properties |
| Bundle size | 10 to 30 KB per app | Scales linearly with component count |
| Ramp-up cost | High; utility vocabulary to learn | Low; CSS is CSS |
| Team scaling | Strong; consistency by default | Style drift past ten contributors |
| Refactor safety | Search-and-replace classes | Rename .module.css classes safely |
| Component kits | shadcn, Catalyst, Park UI | Few; bring your own |
| Server Components | Works (no runtime) | Works (no runtime) |
| Animation | Plugin or arbitrary values | Native CSS |
Migration cost
Mixing both is fine in transition. A full migration costs about one engineer-week per 50 components.
- CSS Modules to Tailwind: install the dependency, port the design tokens to
tailwind.config.ts, then convert components leaf-first. Keep the.module.cssfiles until the component is fully ported; do not run mixed styles on the same element. - Tailwind to CSS Modules: rarer, usually triggered by a brand overhaul. Extract Tailwind classes to CSS rules per component; the
@applydirective eases the transition but should not be the destination (see tailwind). - Coexistence: a Tailwind app can have a few CSS Modules for bespoke surfaces (pricing page, marketing landing) without breaking the design system.
Recommendation
- Product team larger than three with a design system: Tailwind plus shadcn.
- Solo developer or two-person team building a bespoke marketing site: CSS Modules.
- Internal admin tools: Tailwind. Consistency matters more than artistry.
- Brand-led marketing site with heavy art direction: CSS Modules per page section.
- Existing styled-components or Emotion codebase: migrate to Tailwind. The runtime cost of CSS-in-JS no longer pays off in 2026.