Why No Tailwind
Anti-Tailwind manifesto: why design tokens and CSS-in-JS beat utility classes
May 7, 2026---json { "title": "Why No Tailwind", "slug": "why-no-tailwind", "date": "2025-12-08T00:00:00Z", "author": ["Jay Griffin"], "type": "post", "description": "Anti-Tailwind manifesto: why design tokens and CSS-in-JS beat utility classes", "tags": ["css", "tailwind", "emotion", "styling", "design-tokens"], "relatedPosts": ["tailwind-rant-blog", "styling-migration"] }
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.