CSS Modules vs Tailwind in 2026: Scoped vs Utility Approaches
·PkgPulse Team
TL;DR
Both are valid, mature approaches — choose based on team workflow preference. CSS Modules keeps styles in CSS files, close to your design vocabulary. Tailwind puts styles directly in markup, eliminating context switching. CSS Modules are great for teams that prefer traditional CSS authoring with modern scoping. Tailwind is great for rapid iteration without leaving the component file.
Key Takeaways
- Tailwind: ~12M weekly downloads — CSS Modules: built into most frameworks, no npm package
- CSS Modules are built into Vite, Next.js, CRA — zero configuration needed
- Tailwind requires learning utility classes — CSS Modules uses standard CSS
- CSS Modules support all CSS features — animations, complex selectors, variables
- Tailwind enables consistent design system — constrained values from config
The Workflow Difference
CSS Modules workflow:
1. Write component JSX
2. Switch to Button.module.css
3. Write .button { ... } styles
4. Back to JSX: className={styles.button}
Tailwind workflow:
1. Write component JSX
2. Add utility classes inline: className="px-4 py-2 bg-blue-600 text-white"
3. Done — never leave the component file
Tailwind wins for speed of iteration. CSS Modules wins for separation of concerns.
Code Comparison
// CSS Modules — styles in separate file
// Button.module.css:
.button {
display: inline-flex;
align-items: center;
padding: 0.5rem 1rem;
border-radius: 0.375rem;
font-weight: 500;
cursor: pointer;
transition: background-color 150ms;
}
.primary {
background-color: #2563eb;
color: white;
}
.primary:hover {
background-color: #1d4ed8;
}
.primary:disabled {
opacity: 0.5;
cursor: not-allowed;
}
// Button.tsx:
import styles from './Button.module.css';
export function Button({ variant = 'primary', disabled, children, ...props }) {
return (
<button
className={`${styles.button} ${styles[variant]}`}
disabled={disabled}
{...props}
>
{children}
</button>
);
}
// Tailwind — everything in JSX
export function Button({ variant = 'primary', disabled, children, ...props }) {
const variants = {
primary: 'bg-blue-600 text-white hover:bg-blue-700',
outline: 'border border-gray-300 text-gray-700 hover:bg-gray-50',
ghost: 'text-gray-700 hover:bg-gray-100',
};
return (
<button
className={`inline-flex items-center px-4 py-2 rounded-md font-medium transition-colors cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed ${variants[variant]}`}
disabled={disabled}
{...props}
>
{children}
</button>
);
}
When CSS Modules Shine
/* Complex animations — natural CSS syntax */
/* Button.module.css */
@keyframes pulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.05); }
}
.loading {
animation: pulse 1.5s cubic-bezier(0.4, 0, 0.6, 1) infinite;
}
/* Complex selectors */
.table tr:nth-child(even) {
background-color: #f9fafb;
}
.form :focus-within {
outline: 2px solid #2563eb;
}
/* Media queries (possible in Tailwind but verbose) */
@media (max-width: 640px) {
.sideNav {
position: fixed;
inset: 0;
transform: translateX(-100%);
transition: transform 300ms;
}
.sideNav.open {
transform: translateX(0);
}
}
Combining Both
Many successful projects use both:
// CSS Modules for complex, component-specific styles
// Tailwind for layout and spacing
import styles from './ComplexChart.module.css';
function Chart({ data }) {
return (
// Tailwind for layout
<div className="w-full h-64 p-4 bg-white rounded-lg">
{/* CSS Module for complex SVG styles */}
<svg className={styles.chartContainer}>
{data.map((point, i) => (
<circle
key={i}
className={`${styles.dataPoint} ${point.highlighted ? styles.highlighted : ''}`}
/>
))}
</svg>
</div>
);
}
When to Choose
Choose Tailwind CSS when:
- Rapid prototyping and iteration speed matter
- Team wants to stay in the component file
- Consistent design system with constrained values
- Already using shadcn/ui or other Tailwind components
Choose CSS Modules when:
- Team prefers writing actual CSS (familiar mental model)
- Complex animations and selectors that are awkward in Tailwind
- Strict separation of styles and markup
- Working on projects where Tailwind's constraint feels limiting
- Migrating from traditional CSS-in-files workflow
Compare CSS Modules and Tailwind package health on PkgPulse.
See the live comparison
View css modules vs. tailwind on PkgPulse →