Why No Tailwind

By Jay GriffinΒ Β Β·Β  December 8, 2025
🏷️ Tags:csstailwindemotionstylingdesign-tokens

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:

No thanks.


The Core Problems

1. Class Soup is Unreadable

.tsx
// 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

.txt
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

.tsx
// 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

.js
// 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

.tsx
// 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

.tsx
// 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:

The Reality:


At Scale, Everyone Does This:

.tsx
// 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

.txt
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:

At which point: Why use Tailwind?


Our Philosophy

"Treat styles as structured data, not magic strings"

Emotion + CSSObject = Structured data approach
Tailwind = Magic string approach

We choose data.


Conclusion

No Tailwind. Ever.

We have:

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.