Why No Tailwind
Anti-Tailwind manifesto: why design tokens and CSS-in-JS beat utility classes
Why No Tailwind
Date: December 8, 2025
Status: Anti-Tailwind Manifesto
Stack: Emotion + Radix + TypeScript
TL;DR
Tailwind = Design tokens + CSS classes + marketing
We already have design tokens (theme objects) and full CSS power (Emotion). Tailwind adds:
- β Learning overhead (hundreds of class names)
- β Messy markup (class soup)
- β Limited customization (predefined values)
- β Extra tooling (config files, build setup)
- β Mixed styling paradigms (when you inevitably need real CSS)
No thanks.
The Core Problems
1. Class Soup is Unreadable
// Tailwind
<button className="inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 bg-primary text-primary-foreground hover:bg-primary/90 h-10 px-4 py-2">
Click me
</button>
// Emotion
<Button variant="primary">Click me</Button>
Winner: Emotion (clean markup, logic elsewhere)
2. You Learn an API Just to Write CSS Anyway
Tailwind requires:
1. Learn CSS (you need it anyway)
2. Learn ~500 utility classes
3. Learn responsive prefixes (sm:, md:, lg:)
4. Learn state variants (hover:, focus:, active:)
5. Learn arbitrary value syntax
6. Learn config file
7. STILL need CSS for complex cases
Emotion requires:
1. Learn CSS (you need it anyway)
2. Learn styled() function (5 minutes)
DONE.
Winner: Emotion (minimal learning, maximum power)
3. Customization is a Nightmare
// Need padding: 17px and a custom gradient?
// Tailwind Option 1: Ugly arbitrary values
<div className="p-[17px] bg-[linear-gradient(135deg,#667eea_0%,#764ba2_100%)]">
// Tailwind Option 2: Bloated config file
// tailwind.config.js gets huge with custom values
// Tailwind Option 3: Fall back to regular CSS
// Now you have Tailwind + CSS = two systems
// Emotion: Just write it
const Card = styled.div({
padding: '17px',
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
});
Winner: Emotion (full CSS power always available)
4. It's Not Actually Powerful
Tailwind = Pre-defined CSS variables with class names
// Tailwind's "power"
module.exports = {
theme: {
spacing: { '4': '1rem' },
colors: { blue: { 500: '#3b82f6' } },
},
};
// Your theme (same thing, more control)
const theme = {
spacing: { md: '1rem' },
colors: { primary: '#3b82f6' },
};
The only difference: Syntax. Tailwind uses classes, we use objects.
Winner: Emotion (same power, better DX)
5. Not Type-Safe
// Tailwind: Just strings (typos not caught)
<div className="bg-blur-500"> {/* Typo! No error */}
// Emotion: Type-safe
const Card = styled.div<CardProps>(({ variant }) => ({
backgroundColor: variant === 'primary' ? 'blue' : 'gray',
// ^^^^^^^^^^^^^^ TypeScript validates this
}));
Winner: Emotion (TypeScript integration)
6. Not AI-Friendly
// Tailwind: Magic strings
className="flex items-center p-4"
// AI has to memorize Tailwind class names
// Can't validate
// Can't compose easily
// Emotion: Structured data
const styles: CSSObject = {
display: 'flex',
alignItems: 'center',
padding: '1rem',
};
// AI understands JavaScript objects
// Can validate types
// Can compose/merge programmatically
Winner: Emotion (data-first, AI-friendly)
The Marketing Deception
What Tailwind Claims:
- β¨ "Utility-first CSS!"
- β¨ "Rapid prototyping!"
- β¨ "Design system in a box!"
- β¨ "Never write CSS again!"
The Reality:
- β Utility-first = Inline styles with extra steps
- β Rapid prototyping = Only fast for simple cases
- β Design system = CSS variables with docs
- β Never write CSS = Until you need customization
At Scale, Everyone Does This:
// Tailwind projects inevitably create components
function Button({ variant, size, children }) {
const classes = classnames(
'inline-flex items-center justify-center rounded-md',
variant === 'primary' && 'bg-blue-500 text-white',
size === 'md' && 'px-4 py-2',
);
return <button className={classes}>{children}</button>;
}
// Now you're doing what Emotion does
// But with worse DX (string manipulation)
If you end up with components anyway, why use Tailwind?
Our Stack is Better
Emotion + Radix + TypeScript:
β
Clean markup
β
Full CSS power
β
Type-safe (CSSObject)
β
AI-friendly (structured data)
β
Composable
β
No learning overhead (just CSS)
β
No config files
β
No class name soup
β
Unlimited customization
Tailwind:
β οΈ Messy markup (class soup)
β οΈ Limited by predefined values
β Not type-safe (strings)
β Less AI-friendly (magic strings)
β API to learn (hundreds of classes)
β Config files for customization
β Workarounds for edge cases
The Proof
When the constraints that justified server-side rendering disappeared (slow devices, slow internet, weak browsers), JavaScript won.
When the constraints that justified "utility-first CSS" disappear (need for speed without components), components win.
Modern development = Component-based styling, not class-based styling.
The Final Word
Tailwind is training wheels that never come off.
You're limited to their system unless you:
- Clutter markup with arbitrary values
- Bloat config with custom values
- Fall back to regular CSS
At which point: Why use Tailwind?
Our 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
Emotion + CSSObject = Structured data approach
Tailwind = Magic string approach
We choose data.
Conclusion
No Tailwind. Ever.
We have:
- Design tokens (theme objects)
- Full CSS power (Emotion)
- Type safety (TypeScript + CSSObject)
- Clean markup (styled components)
- AI-friendly patterns (structured data)
Tailwind would add complexity without adding power.
End of discussion. π«
P.S. If you're reading this and thinking "but Tailwind works for me," that's fine. Use what works. This document explains why it doesn't work for us and our AI-first, type-safe, data-driven approach.