Back to blog

Why I Stopped Using Tailwind Utilities for Design Systems

2 min read

I love Tailwind CSS. I use it on every personal project. But if you ask me to build a multi-tenant enterprise UI component library used by 50 different micro-frontends, I am putting strict guardrails on how we use utility classes.

We need to talk about the reality of scaling utility-first CSS across massive teams.

The Chaos of Primitive Classes

Picture a standard Primary Button. In Tailwind, it looks something like this: className="bg-blue-600 hover:bg-blue-700 text-white font-medium py-2 px-4 rounded-md focus:outline-none focus:ring-2".

This is incredibly fast to write. But what happens when the design team decides that all primary buttons across all 20 of your company's web products need to be slightly more rounded and use a different hover transition? Do you really want to do a global Regex search-and-replace for that exact string of classes across thousands of files?

If you are just copy-pasting utility strings everywhere, you haven't built a design system. You have just built inline styles with a shorter syntax.

The Component Boundary

Tailwind is at its best when it is restricted to the boundaries of tightly controlled React components.

If you are building a design system, the consumer of your <Button> component should never see a Tailwind class. They should see semantic props like variant="primary" or size="lg". Inside the component, you map those props to the Tailwind classes using a tool like CVA (Class Variance Authority) or Tailwind Merge.

The moment you allow engineers to pass random Tailwind classes down into your core UI components to override padding or margins, you completely destroy the consistency of your design system.

Use utility classes to build your foundation. But expose those utilities strictly through a highly controlled, strongly-typed component API. Hide the mess. Enforce the constraints. That is how you scale a frontend architecture.