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 .tsx and .module.css to 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.css colocates 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

DimensionTailwindCSS Modules
Where styles liveInline in JSX as classesSibling .module.css
Design tokensFirst-class in configHand-rolled CSS custom properties
Bundle size10 to 30 KB per appScales linearly with component count
Ramp-up costHigh; utility vocabulary to learnLow; CSS is CSS
Team scalingStrong; consistency by defaultStyle drift past ten contributors
Refactor safetySearch-and-replace classesRename .module.css classes safely
Component kitsshadcn, Catalyst, Park UIFew; bring your own
Server ComponentsWorks (no runtime)Works (no runtime)
AnimationPlugin or arbitrary valuesNative 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.css files 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 @apply directive 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.