Technical
Tailwind Utility Patterns for Maintainable CSS
Tailwind's reputation is 'ugly class soup.' That is true when you misuse it. When you embrace the utility pattern properly, Tailwind produces some of the most maintainable CSS I have shipped. Here are the patterns that separate clean Tailwind from a mess.
The Core Insight
Tailwind does not eliminate CSS. It eliminates the middle layer: naming things. Instead of .header-inner-wrapper-large, I write flex items-center justify-between p-4. The class names are no longer abstractions I have to remember; they are the CSS itself.
Pattern 1: Extract With Components, Not CSS Classes
When the same utility combo appears five times, I do not extract a CSS class. I extract a React component:
// Bad: CSS class abstraction
.btn-primary { @apply px-4 py-2 bg-blue-500 text-white rounded; }
// Good: component abstraction
function PrimaryButton({ children }) {
return <button className="px-4 py-2 bg-blue-500 text-white rounded">{children}</button>;
}Components encapsulate behavior, not just style. A PrimaryButton can have hover state, disabled state, loading spinner. A CSS class cannot.
Pattern 2: Use the Config for Design Tokens
Hardcoded colors (text-[#0ea5e9]) are a code smell. Define them in tailwind.config.js:
module.exports = {
theme: {
extend: {
colors: {
brand: {
500: '#0ea5e9',
600: '#0284c7',
}
}
}
}
};Now text-brand-500 works everywhere. Change the value once, update the whole app.
Pattern 3: Group With Whitespace
Long class lists feel chaotic. I group them by purpose with whitespace and sometimes comments:
<div className={`
flex items-center justify-between
p-4 rounded-lg
bg-white dark:bg-slate-900
border border-slate-200 dark:border-slate-700
`}>Layout, spacing, colors, borders. Each group on its own line. Scan, do not read.
Pattern 4: Avoid @apply
@apply looks like it gives you the best of both worlds. It actually gives you the worst. You lose Tailwind's purge optimization and you reintroduce the naming problem. I only use @apply for true utility patterns that exist independent of any component.
Pattern 5: Dark Mode via Class Variants
Every color utility has a dark: variant. I write dark mode styles inline with light styles:
<div className="bg-white text-slate-900 dark:bg-slate-900 dark:text-white">No separate dark mode CSS file. No theme context. Just utilities with variants.
When Tailwind Hurts
For heavily animated or complex CSS (keyframes, grid layouts with named areas), Tailwind gets awkward. I use a plain CSS file for those cases and keep Tailwind for the 95% of styling that is padding, colors, and flexbox.
The Team Benefit
New developers read my Tailwind code and understand it immediately. They do not hunt through CSS files trying to figure out what .header-nav-container does. The styles are right there next to the markup.
See the Tailwind documentation and the Tailwind CSS best practices guide for the official patterns that match this approach.
RELATED READING
The Consulting Shift I Am Making In Year Two
After a year of writing and building, my consulting practice is changing shape. Shorter engagements. Sharper outcomes.
ReadThe Frontend Shift: Shipping Less JavaScript In Year Two
A year ago I reached for Next.js for everything. This year I often reach for nothing.
ReadThe Serverless Lesson I Would Write On A Sticky Note
After a year of shipping serverless projects, one rule explains most of the wins and all of the losses.
Read