We Used to Be CSS Modules Purists
Two years ago, if you'd asked us, we'd have said CSS Modules for everything. Scoped styles, proper separation of concerns, no utility class soup in your JSX. It was clean, it was principled, and it was how CSS "should" be written. Then we built three consecutive projects with Tailwind and something shifted.
We're not Tailwind evangelists now — we still use CSS Modules on some projects. But we've stopped being dogmatic about it, because the tradeoffs are more nuanced than the Twitter debates suggest.
Tailwind's Real Advantage: Velocity
The single biggest win with Tailwind is speed of development. On our last project — a SaaS dashboard with about 120 unique UI components — the frontend team shipped screens roughly 30% faster with Tailwind compared to our historical velocity with CSS Modules. That's not a scientific measurement, but it's consistent across three projects. The reason: you don't context-switch between files. Everything is in one place. You see the styling inline with the markup and can iterate without jumping back and forth.
For agency work where we're building new UIs from scratch under tight deadlines (which is most of our work), this velocity difference is significant. A week saved on frontend is a week the client doesn't pay for.
CSS Modules' Real Advantage: Maintainability
Here's where CSS Modules fight back. On a long-lived product that multiple teams contribute to over years, Tailwind's inline classes become hard to maintain. We maintain a 3-year-old React app with CSS Modules and the styles are still clean and understandable. By contrast, a Tailwind project we built 18 months ago has JSX files with className strings that are 200+ characters long. Reading those is like parsing encrypted text.
We've tried to mitigate this with @apply (extracting Tailwind utilities into CSS classes), but honestly, @apply defeats the purpose of Tailwind. If you're writing @apply everywhere, you're just writing CSS with extra steps. And the Tailwind team themselves have said they regret adding @apply.
What We Actually Recommend Now
For new projects with a 6-12 month build timeline (typical client engagement): Tailwind. The velocity advantage outweighs the maintainability concerns at this scale. Use component composition aggressively — if a set of classes repeats, extract a component, not a CSS class.
For product companies building something they'll maintain for 3+ years with a growing team: CSS Modules, or honestly, vanilla-extract if your team can handle TypeScript-first styling. The type safety and explicit scoping pay dividends at scale.
For design-system-heavy projects where you're implementing a detailed Figma design system: CSS Modules. Design tokens map more naturally to CSS custom properties than Tailwind's config, and complex animations are significantly easier in plain CSS than in Tailwind's constrained utility model.
The Tools We Pair With Each
Tailwind projects: we use tailwind-merge to handle conflicting classes (a must for component libraries), clsx for conditional classes, and the Prettier plugin for consistent class ordering. We also set up ESLint rules to enforce a maximum className length, which forces extraction into components.
CSS Modules projects: we use typed-css-modules for TypeScript type generation from CSS files, and Stylelint with a strict config to enforce conventions. We also use CSS custom properties for our design tokens rather than Sass variables, because they work at runtime and integrate with both approaches.
One more thing: don't mix both approaches in the same project unless you want to drive your team insane. We tried it once — "Tailwind for layout, CSS Modules for component-specific styles" — and it was a mess. Pick one and commit.