Why I Choose CSS-in-JS Over Tailwind
A personal defense of CSS-in-JS over Tailwind's utility-first approach
Why I Choose CSS-in-JS Over Tailwind
Note: I don't hate Tailwind. I've used it personally and I even enjoyed using it. And if it works for you I am totally fine with that. This is just my inner monologue defending my choice for CSS-in-JS. Think of it as a personal decision log, not a call to arms.
🎺 UTILITY FIRST! 🎺
📣 UTILITY FIRST! 📣
🔥 UTILITY FIRST! 🔥
[crowd goes wild]
[nobody asks what it actually means]
[class soup intensifies]
[arbitrary value syntax enters the chat]
[config file grows to 500 lines]
[someone whispers "isn't this just inline styles"]
[they are escorted from the building]
UTILITY FIRST! UTILITY FIRST! UTILITY FIRST!
The "Super Styling" Realization
CSS-in-JS isn't a replacement for CSS or a workaround—it's CSS with superpowers.
It's CSS that got:
- JavaScript (computation, logic, state)
- TypeScript (validation, autocomplete)
- Real scope (encapsulation, not conventions)
- Composability (merge, spread, conditionals)
- Colocated logic (styles live with behavior)
It's strictly more powerful. You get all of CSS plus everything else.
Tailwind, on the other hand, is CSS minus flexibility, plus a learning tax.
The Thing Nobody Talks About
At scale, every Tailwind project does this:
// You start with inline classes
<button className="inline-flex items-center justify-center rounded-md bg-blue-500 text-white px-4 py-2 hover:bg-blue-600">
Click me
</button>
// Then you realize this is unsustainable and create...
function Button({ variant, size, children }) {
const classes = classnames(
'inline-flex items-center justify-center rounded-md',
variant === 'primary' && 'bg-blue-500 text-white hover:bg-blue-600',
size === 'md' && 'px-4 py-2',
);
return <button className={classes}>{children}</button>;
}
// Congratulations, you're doing component-based styling
// But with string manipulation instead of actual objects
If you end up with components anyway, why use Tailwind?
You're just doing CSS-in-JS but with worse DX.
The Marketing Trick
Tailwind's genius was rebranding inline styles as "utility-first CSS" and suddenly it sounds sophisticated.
The pitch is basically:
- "Writing CSS is hard and messy"
- "What if we gave you predefined classes for everything?"
- "UTILITY-FIRST! UTILITY-FIRST! UTILITY-FIRST!"
- "Look how fast you can build things!"
- (Don't think about customization)
- (Don't think about class soup)
- (Don't think about the learning curve)
It's the words "utility classes" said over and over until people just... accepted it as better without questioning whether it actually is.
The Real Comparison
What You Actually Get
Tailwind:
<div className="flex items-center justify-between p-[17px] bg-[linear-gradient(135deg,#667eea_0%,#764ba2_100%)]">
- ❌ Class soup in markup
- ❌ Arbitrary values look ugly
- ❌ Magic strings (not type-safe)
- ❌ Limited by predefined values
- ❌ Need to learn their API (~500 classes)
CSS-in-JS (Emotion):
const Card = styled.div({
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
padding: '17px',
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
});
<Card>Content</Card>
- ✅ Clean markup
- ✅ Full CSS power always available
- ✅ Type-safe (CSSObject)
- ✅ Unlimited customization
- ✅ Just need to know CSS
The AI Argument
This one's forward-looking but important:
// Tailwind: Magic strings
className="flex items-center p-4"
// AI has to memorize class names
// Can't validate
// Can't compose easily
// CSS-in-JS: Structured data
const styles: CSSObject = {
display: 'flex',
alignItems: 'center',
padding: '1rem',
};
// AI understands JavaScript objects
// Can validate types
// Can compose/merge programmatically
Structured data > magic strings for any kind of programmatic manipulation.
The Customization Problem
Tailwind is fast until you need something custom. Then you either:
- Use arbitrary values (ugly, defeats the purpose)
- Bloat your config file (now you're maintaining design tokens anyway)
- Fall back to regular CSS (now you have two systems)
With CSS-in-JS, customization is just... writing CSS. No workarounds, no config files, no falling back to a different system.
My Philosophy
"Treat styles as structured data, not magic strings"
- Data is composable
- Data is testable
- Data is type-safe
- Data is transformable
- Data is AI-friendly
The entire JavaScript ecosystem moved from strings to structured data:
- HTML strings → JSX
- JSON strings → Objects
- Manual DOM → Virtual DOM
Why would CSS be different?
The Bottom Line
Tailwind is training wheels that never come off.
You're constrained by their system unless you add workarounds, at which point—why use Tailwind?
CSS-in-JS is CSS 2.0. All the existing power plus programmatic control.
I'm not saying Tailwind is bad. I'm saying CSS-in-JS is super styling—and once you have superpowers, you don't go back to predefined utility classes.
My Stack
Emotion + Radix + TypeScript
- Clean markup
- Full CSS power
- Type-safe
- Composable
- No learning overhead (just CSS)
- No config files
- Unlimited customization
It's not about being anti-Tailwind. It's about choosing the approach that gives me the most power with the least constraints.
If you disagree, that's cool. Use what works for you. This is just my reasoning for why CSS-in-JS works better for the way I build things.