Comparison
Tailwind or CSS Modules? Velocity against isolation
The verdict
For a new application, pick Tailwind v4. It is the default in shadcn/ui, in most framework starters, and in essentially every AI code generator, its Rust-based Oxide engine cut cold builds on a 15,000-utility design system from roughly 840 ms to 170 ms with incremental rebuilds measured in microseconds, and production CSS typically lands between 5 and 20 KB. Pick CSS Modules when you are publishing a component library that other teams install, when the codebase is a large existing SCSS estate you are migrating incrementally, or when strict per-component isolation is a design system requirement rather than a preference. This site is built with Tailwind v4, so treat the verdict as disclosed bias plus a real reason.
The gap between these two is the narrowest it has ever been. Tailwind v4 moved configuration into CSS with @theme, replaced the PostCSS plugin, JIT compiler and content scanner with one Rust engine, and reports full rebuilds 3.5 to 10 times faster with incremental rebuilds over 100 times faster. CSS Modules did not change, because it is a bundler feature rather than a product - and that stability is exactly its pitch.
Tailwind vs CSS Modules across authoring and maintenance
Neither approach is slow in the browser. What separates them is where the cost lands: in your markup, in your build config, in onboarding, or in the day you have to refactor a shared component.
| Dimension | Tailwind v4 | CSS Modules |
|---|---|---|
| Authoring model | Utilities in the class attribute. No naming, no file switching, styles live where the markup is. | Ordinary CSS in a .module.css file, imported as an object. You name things, and names carry meaning. |
| Build tooling | One dependency and one CSS entry file. Oxide handles scanning, generation and optimization in a single pass. | Built into Vite, webpack and Parcel with no extra install. Nothing to upgrade and nothing to break. |
| Output size | Only the utilities you used are emitted, typically 5 to 20 KB for a whole application. | Scales with what you wrote. Dead rules survive forever unless a linter or a person removes them. |
| Design tokens | One @theme block defines colours, spacing and fonts, and every utility derives from it. | Custom properties work well, but the scale, the naming convention and the discipline are all yours to invent. |
| Component ecosystem | shadcn/ui, Headless UI and most template marketplaces ship Tailwind classes you can paste and edit. | No central hub. You port third-party markup by hand, which is honest work but it is work. |
| Readability | Long class strings are the real cost. Diffs get noisy and complex responsive states get hard to scan. | Markup stays clean. Media queries, pseudo selectors and keyframes read exactly like CSS because they are. |
| Portability | Markup carries a Tailwind dependency. A consumer of your component must run Tailwind too. | Compiled output is plain scoped CSS, which is why libraries shipped to unknown consumers prefer it. |
When each approach is the right call
Choose Tailwind when
- You are building application UI where components are consumed inside your own codebase, not published to strangers.
- The team is small and nobody wants to own a CSS architecture, a naming convention, or a spacing scale.
- You want shadcn/ui or a Tailwind template as a starting point rather than building primitives from zero.
- Dead style elimination should be automatic. Delete the component, and its CSS stops being emitted.
- You use AI assistance heavily, where utility classes in the markup are what models produce most reliably.
Choose CSS Modules when
- You publish a component library to npm and cannot require consumers to configure a styling framework.
- The design system is strict and enforced, and inline utilities would let anyone bypass it in a hurry.
- The styling is genuinely complex: multi-step keyframes, deep selector logic, print stylesheets, heavy container queries.
- You are migrating a large SCSS codebase and want to move file by file with no framework rewrite in between.
- Your team has real CSS depth and treats readable markup as a maintenance asset, which is a defensible position.
The failure modes on each side
Tailwind fails when a team treats @apply as the escape hatch. Pulling utility strings into named CSS classes recreates every problem Tailwind was meant to remove - a second naming system, specificity questions, and dead rules that nothing eliminates - while keeping the framework dependency. The correct move is to extract a component, not a class. If a button appears in forty places, make a Button component; do not make a .btn rule out of applied utilities.
CSS Modules fails on drift. Without an enforced token layer, three developers produce three spacing scales and four blues within a quarter, and none of it is visible in review because each file looks reasonable on its own. Custom properties solve this, but only if somebody owns them and the linter enforces them. Teams that succeed with CSS Modules invariably have a person or a document doing the job Tailwind's config does automatically.
The second Tailwind cost is human rather than technical. A responsive, dark-mode, hover-state element can carry twenty-plus classes, and that line is genuinely harder to read than the equivalent CSS block. Multi-line class formatting and the official Prettier plugin for class sorting help considerably, and both are worth adopting on day one rather than after the first argument in a pull request.
Worth saying plainly: this is a reversible decision, unlike most of the choices on this site. Converting a component from CSS Modules to Tailwind or back is a contained, mechanical change with no data migration, no downtime, and no ecosystem cost. Do not spend a week deciding. Pick the one your team can move fastest in, ship, and revisit if it hurts. The Tailwind documentation is at tailwindcss.com; CSS Modules is documented by whichever bundler you already use.
Where this fits in a full stack
The CSS and UI layer covers the component libraries built on each approach, plus the CSS-in-JS options this comparison left out. For the utility names and responsive prefixes themselves, the Tailwind CSS cheatsheet is the faster reference.
Styling is one layer of a build. The static and content site stack guide shows how it fits with a framework, a content source and a host. Other close calls are in the comparisons index.