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.