Tailwind CSS might not be for you

benface

Benoît Rouleau

Posted on May 8, 2021

Tailwind CSS might not be for you

Disclaimer: This article is my version of Tailwind CSS: Adds complexity, does nothing. I respect the original author’s opinions, but I think there’s a better way to criticize Tailwind CSS. Here goes.

If you work in the front-end, you’ve probably heard a lot about Tailwind CSS, a CSS framework, much like Bootstrap. Much unlike Bootstrap, however, Tailwind takes a different approach – it is almost exclusively “utility classes”.

And it might not be for everyone.

Before we start, let me try to explain what a utility class is. Let’s say that you have many components, and many of them need to have the CSS declaration display: flex;. Instead of writing that over and over in your CSS, you create a class called flex:

.flex {
  display: flex;
}
Enter fullscreen mode Exit fullscreen mode

Then, in every component that needs to be flexed, you add that flex class.

This is not a bad thing. I have written and used utility classes a great deal myself, especially when I’m writing CSS without the aid of CSS-in-JS solutions or a preprocessor like Sass/SCSS.

What Tailwind does is take that concept to the extreme, with the idea being that you almost never have to write CSS, you just add different classes to your HTML based on what styles you need to apply.

Which is an interesting choice, because...

This is embracing inline styles

Back before stylesheets were a thing, HTML had elements such as <font> and <center> to apply some basic styles directly in the markup, much like the style attribute that came along with CSS. But while inline styles are still a thing nowadays, we know better than to use them since we have stylesheets now, which let us “separate concerns”: HTML is for content and structure, CSS is for presentation.

However, Tailwind doesn’t adhere to that idea, and goes back to the 90’s way of mixing content and presentation in the same file. So why not just use inline styles then? Writing <div class="flex">foo</div> has the same exact effect as writing <div style="display: flex;">foo</div>. Well, it turns out there are a couple reasons, as outlined in the Tailwind documentation. Notably, inline styles don’t support media queries or pseudo-class selectors such as :hover or :focus, so you can’t have responsive or dynamic styles with them. Building a whole app or website with inline styles would be impossible for that reason alone, unless you decide to pretend that mobile devices don’t exist. If that’s not reason enough, Tailwind makes a strong argument for “designing with constraints”:

Using inline styles, every value is a magic number. With utilities, you're choosing styles from a predefined design system, which makes it much easier to build visually consistent UIs.

Indeed, Tailwind’s theme configuration is one of its greatest strengths. It prevents your stylesheet from containing 69 unique font sizes and some background colors that are different but so similar that they should clearly be the same (e.g. #ffe42e and #ffe322). It also helps developers design faster and be more confident that they’re not introducing visual inconsistencies.

But even though Tailwind improves a lot on inline styles, it still embraces their philosophy and suggests that it’s totally fine – even desirable – to mingle presentation with content. Consequently, some of the arguments that you could make against using inline styles are also arguments against using Tailwind. I know it seems a bit lazy to rehash other users’ criticisms of inline styles to explain why Tailwind might not be for you, but let’s do it anyway:

It’s WET, not DRY

When you want to change your site’s styling in a major way, if you’ve used utility classes, you need to go through each use of those utility classes – that is, every component – and visually determine what needs to be updated. For example, let’s say that your company’s primary color is blue. You’ll have lots of blue stuff in your website, marked with classes like text-blue-700 or bg-blue-500, which represent different shades of blue. And that’s fine until your company decides to rebrand, and all of the buttons on the site – but only the buttons – need to be red.

If you were using regular old CSS, you would probably have a class called button. You would just go into that class in your CSS and change a single line: background-color: red;. Any element that uses that class definition would now be red.

Instead, with Tailwind, you have to go through each component and manually change bg-blue-500 to bg-red-500. And with 1000 edits comes 1000 opportunities to introduce a bug. It is almost a textbook definition of why the DRY principle is in place.

That is, unless you bring back the abstraction that you lost by replacing button with a bunch of utility classes in your HTML. In my experience, Tailwind works best if anything that used to be a “CSS component” (like the button class) is made a “template component” (a reusable file that includes both the markup and the styling). That makes sense when you think about it, and you end up removing even more duplication: not just the Tailwind classes which now live in a single file instead of 1000, but any attribute (think ARIA) or child element of the component (think button__icon). Turns out your code’s DRYness is up to you, not up to Tailwind!

The above assumes that you’re using some kind of component library (e.g. React, Vue, Svelte, etc.) or templating language that supports partials (Twig, Blade, PHP, etc.). If you’re not, or if you find that it would be cumbersome to create a component or partial for a simple button, that’s totally fine. You don’t have to change your abstraction model, you can still use CSS itself as your “component layer”. That’s where Tailwind’s @apply feature comes in handy: you keep your button class, but instead of writing background-color: red;, you write @apply bg-red-500;. That way, you’re still using the theme configuration instead of a hard-coded (magic) value. This is similar to what you can do with preprocessors or CSS variables, but using the Tailwind API.

HTML is traditionally concerned with structure, not styling

People talk about separation of concerns a lot in development. CSS Modules (and especially .vue files) have done a lot to dispel the notion that you need to segregate structure, behavior, and style of each building block of your site in separate folders, but there is something to be said for separating concerns. That is, each part of your code should be “loosely coupled and highly cohesive.”

In other words, your HTML (structure syntax) shouldn’t have information about what the styles should be; it should only contain information about the structure of the page. Indeed, the ultimate reason for the invention of CSS, the whole point of the entire enterprise of CSS... was specifically so that you could separate content from presentation.

And yet, Tailwind embraces the idea of inline styles, which goes against that whole concept. Why is that? Adam Wathan, the author of Tailwind, has written an article about how separation of concerns is “a straw man” and we should instead think of it in terms of “dependency direction”. It’s a long read, but it’s worth it to understand where Tailwind comes from.

It turns out Tailwind, like most CSS frameworks, is targeted towards developers who prefer writing HTML that depends on CSS, over CSS that depends on HTML. Adam mentions that both approaches are perfectly valid, and it comes down to “what’s more important to you in a specific context”. Tailwind takes the first approach, and goes as far as it can with it. As a result, developers can build custom UIs right in the HTML because the CSS provides all the necessary building blocks.

When we write code, we write it for two audiences: the first is the computer itself, which doesn’t care how the code looks so long as it runs, and the other is our fellow programmers. The easier it is for them to quickly identify what parts of your program are and how they interrelate, the more quickly they can fix bugs, add features, and bring value to the organization. Tailwind makes it easy not only to build UIs without switching context, but also to understand what each element looks like at a glance, since the styles are right there in the same file.

The flip side of losing “semantic” class names in favor of utility classes is that it becomes not as obvious what a given piece of HTML represents in terms of content. Thankfully, that is easily mitigated by using well-named components, or adding comments or even classes that do nothing but describe what an element is (as the first class, so it’s not lost in the sea of utilities, obviously).

It’s hard to read at first

If you look at some HTML with Tailwind in it, you might say to yourself that the HTML looks “busy” or even “ugly.” That's true, but some say you get used to it.

The real catch is that you have to learn all these classes before you can be productive with it, even if you know CSS really well. Tailwind is full of semantically obscure abbreviations such as w for width and h for height. The framework tries to find a balance between terseness and expressiveness, but it can definitely feel cryptic at first.

Here's an example from Aleksandr Hovhannisyan.

This Tailwind code:

<div class="w-4 h-4 rounded text-white bg-black py-1 px-2 m-1 text-sm md:w-8 md:h-8 md:rounded-md md:text-base lg:w-12 lg:h-12 lg:rounded-lg lg:text-lg">
  Yikes.
</div>
Enter fullscreen mode Exit fullscreen mode

could be expressed as:

<style>
  .thing {
    width: 1rem;
    height: 1rem;
    color: white;
    background-color: black;
    padding: 0.25rem 0.5rem;
    margin: 0.25rem;
    border-radius: 0.25rem;
    font-size: 0.875rem;
    line-height: 1.25rem;
  }

  @media screen and (min-width: 768px) {
    .thing {
      width: 2rem;
      height: 2rem;
      border-radius: 0.375rem;
      font-size: 1rem;
      line-height: 1.5rem;
    }
  }

  @media screen and (min-width: 1024px) {
    .thing {
      width: 3rem;
      height: 3rem;
      border-radius: 0.5rem;
      font-size: 1.125rem;
      line-height: 1.75rem;
    }
  }
</style>

<div class="thing">Yikes.</div>
Enter fullscreen mode Exit fullscreen mode

As you can see, there are pros and cons to each approach. The second example is much more expressive (especially if you don’t know Tailwind), but it’s a lot more code, and the styles are separate from the element they are affecting. The Tailwind code, on the other hand, is short and you don’t need to open another file to understand how it’s styled. You may find it cryptic, but after just a couple days of using it, you should be able to decipher it effortlessly.

It’s worth noting that Tailwind classes are arranged horizontally, while the CSS is written vertically. The wider text is, the harder it is for a reader’s eyes to jump to the next line, and the harder it is to find the one particular word you’re looking for in a wall of horizontal text. That’s part of the reason why Tailwind classes are terse (in addition to typing speed and file size). Note that there are different ways to mitigate lines getting too long (enabling wrapping in your IDE, adding line breaks, using @apply selectively, etc.), but it is a potential issue to be aware of.

Again, this is a matter of preference. Tailwind might be for you, or it might not, but it’s hard to know without giving it a real try.

You lose a lot of the features built into standard CSS

...if you insist on not using any custom CSS. But realistically, most Tailwind projects have some custom CSS, which is totally fine – Tailwind itself is a PostCSS plugin, meaning it runs on CSS source files, not instead of.

So if you want some specific styling rules, for instance to add some margin between p tags inside a description class, you’ll have to write custom CSS, though nothing prevents you from using @apply:

.description p + p {
  @apply mt-4;
}
Enter fullscreen mode Exit fullscreen mode

Note that there are also lots of plugins, including some official ones such as Typography and Forms, for extending Tailwind’s core functionality.

It solves a problem that you may not have encountered

We’re all different. We work on different projects, have different methodologies, use different tools. One tool cannot claim to solve a problem that everyone is having. The best it can do is exist for the people who are experiencing the specific problem it was built to solve, and provide great documentation and other resources to learn about how it can make your work, or your life, easier.

Tailwind does just that. It’s not for everyone. If you’ve read Adam Wathan’s CSS Utility Classes and "Separation of Concerns" article and couldn’t relate, I’m happy to tell you that Tailwind is probably not for you. If writing custom CSS is what you enjoy the most, or you need to apply different stylesheets to the same HTML to radically change how it looks, Tailwind is not for you. That’s OK! Go build great things with your favorite tools.

Nothing is perfect

Something else will come along eventually, solving some of Tailwind’s problems, and maybe it will introduce new problems that we can’t even imagine. Maybe it will be a core web technology, who knows. But in the meantime, if you decide that Tailwind is for you or your team, it’s going to provide a great developer experience with some of the best documentation I’ve ever seen in an open source project, your CSS is going to be as small as ever, and after a while, you might just wonder how you ever did CSS any other way.

💖 💪 🙅 🚩
benface
Benoît Rouleau

Posted on May 8, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related