---
title: "Tailwind vs CSS Modules"
slug: "tailwind-vs-css-modules"
category: "comparisons"
tags: ["comparisons", "tailwind", "css-modules", "frontend", "styling"]
status: "stable"
last_updated: 2026-05-14
summary: "Pick Tailwind for design-system speed and consistency across a team; pick CSS Modules when each component owns its styles and the design is bespoke."
related: ["[[frontend/tailwind]]", "[[frontend/css]]", "[[frontend/css-cascade-layers]]", "[[frontend/shadcn]]", "[[frontend/react]]", "[[frontend/index]]"]
---

## 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 [[frontend/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 [[frontend/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 [[frontend/css-cascade-layers]] and [[frontend/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.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 [[frontend/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.

## Related

- [[frontend/tailwind]]
- [[frontend/css]]
- [[frontend/shadcn]]
- [[frontend/css-cascade-layers]]
- [[frontend/index]]
