Skip to content

Tailwind isn’t a Design System (and that’s okay)

886 words • 4 min to read

I’m a big fan of utility class CSS. I use Tachyons—a lightweight alternative to Tailwind—on my personal site. It’s dead simple and saves me time and effort. But while utility classes are great for solo projects, things get more complicated when you try to scale them across a large team.

This isn’t a critique of Tailwind or utility classes as a concept—they’re great, especially for solo developers or very small teams. But in larger organizations, like when you have a frontend team of 10+ developers, Tailwind can cause just as many problems as it solves.

Let me explain.


What’s the allure of Tailwind?

Speed and perceived simplicity.

Tailwind’s big selling point is that developers don’t have to write custom CSS. You see a design in Figma, and you translate it directly: p-4 for 16px padding, bg-white for a white background, and so on. This can help avoid situations where different developers name similar classes differently—like one using .card-padding and another using .tile-padding for the exact same spacing.

So far, so good.

But what happens when a design calls for 15px padding? One dev might round up to p-4 (16px), another might round down to p-3 (12px), and yet another might use p-[15px], effectively creating a new class.

Now, you might say, “If the team agreed to use Tailwind, then designs in Figma shouldn’t use 15px padding in the first place.” Fair—but it’s not that simple. (See “Consistency through code” below.)

Say you want to change all card backgrounds from white to grey. You search for bg-white, but now you risk changing buttons or other sections too—because utility classes don’t tell you where they’re used. Tailwind doesn’t distinguish between a card, a section, or a button. That knowledge lives in the developer’s head (or in documentation, which starts to defeat the purpose of using Tailwind. More on that below.)


Utility classes aren’t a substitute for a design system

Many teams adopt Tailwind because it’s “flexible.” You can build “anything” with it. But that’s exactly the problem. You’re not building anything—you’re building a very specific thing. So you don’t need flexibility—you need consistency.

Tailwind’s verbose syntax becomes more unwieldy when you factor in dark mode and responsive design.

Want a background that’s light red in light mode and dark red in dark mode?

bg-red-200 dark:bg-red-800

Want card padding to adjust between mobile and desktop?

sm:p-2 lg:p-4

That’s fast to write—you don’t need to create custom classes or media queries.

But… where does the mapping between red-200 and red-800 live? Or between p-2 and p-4?

In the developer’s or designer’s head!

Unless you document it. But once you’re writing docs to enforce consistent use of utility classes, are you really saving time? You’re actually building your own design system on top of Tailwind.

Compare that with using a semantic class like product-card, which already handles different themes and viewports behind the scenes.


Code enforces consistency

Figma designs should aim for consistency, but ultimately, code is the final product. A perfectly consistent Figma file is irrelevant if the implementation isn’t. Likewise, inconsistencies in Figma don’t matter as long as they don’t carry through to production code.

The idea that Figma should be the single source of truth for consistency falls short. Figma has helpful tools like components, styles, and now variables—but it’s still easy to accidentally “detach” things, especially during experimentation. Code, on the other hand, requires deliberate effort to introduce inconsistencies. Accidentally typing p-13 is far less likely than detaching a style variable in Figma.

Often, the push to create a super-flexible CSS system stems from a lack of collaboration between design and dev teams. A dev might think, “Who knows what the designers will change next? We need something that can handle anything.” But imagine if both teams worked together to define naming conventions and constraints from the start!

A well-defined system can live in both CSS and Figma. It helps both sides stay aligned. Naming variables semantically—like padding-card or padding-section—provides a built-in check. If someone tries to use padding-section on a product card, it raises red flags.


What utility classes are GREAT for

  • CSS Grid. I’ll take grid grid-cols-2 sm:grid-cols-3 over one-off CSS classes any day.
  • Solo projects. On my personal site, utility classes are perfect. I know the patterns, and if I use 12px padding on one page and 16px on another, that’s fine. It’s a small project.
  • Spacing, with constraints. Once you define custom utility classes or CSS variables for spacing—like padding-card, padding-section—you can apply them consistently across components and breakpoints.

The issue isn’t utility classes themselves. It’s relying on an enormous set of them without any guidelines.


TL;DR.

You pay for shortcuts down the road. Tailwind’s flexibility becomes a liability when you’re trying to build something consistent with a team.

The ability to build “anything” is less valuable than the ability to build one thing well. And doing that requires adding constraints—not avoiding them.