Blog post cover
Arek Nawo
20 May 2020
9 min read

My mixed feelings about Tailwind CSS

There’s a lot of hype going around in Web Development. Every now and then a new framework/library/tool appears that gets the attention of many developers, possibly even to a point of being called “the next big thing”.

A while ago, I decided to leave my JavaScript comfort zone, to see what’s “the next big thing” in other parts of Web Development such as HTML, or CSS. I quickly discovered that it’s now Tailwind CSS - utility-first CSS framework. So, why’s that, what are my personal thoughts about it?

Utility-first CSS

Let’s first discuss what utility-first CSS even means as it’s not only a cool marketing term. You see, Tailwind is basically a set of small CSS class names that you can use to change certain styles of your element. Consider the code snippet below:

<button
  class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
>
  Button
</button>

Here you can see an example button, styled using various different Tailwind utilities. We’ve got e.g. text-white to set the color to white, py-2 to set vertical (top and bottom) padding to what Tailwind indicates as 2 ( 0.5rem by default), and hover:bg-blue-700 to set the background color of the button to Tailwind’s 700 shade of blue ( #2b6cb0 by default).

Overall, I think you get the idea - a giant set of different class names with an arguably fairly understandable naming scheme. But what are the pros and cons of such a solution?

Advantages

The first thing people usually ask when introduced to Tailwind is “why not just set a CSS property?”. That’s a pretty logical question. Why use class names like text-white instead of just setting color: white directly on a class name dedicated to the specified element?

Utility-first

Here, it’s pretty important to understand the possibilities of utility-first classes. First off, they’re very reusable. Instead of repetitively writing color: white in multiple classes, you just drop the text-white class and that’s it! Plus, you don’t have to create it yourself since the library already does it for you.

Next up, no one says that single utility must set only a single property (although that’s how things are in most cases). Tailwind utilities like clearfix make for very convenient and ready-to-use solutions that you’d otherwise have to search the web for.

And speaking of convenience, Tailwind’s utilities like px-{n} accelerate the whole design process in a weird way. Instead of having to think about perfect values for padding, margin, width, or whatever, you’re limited only to a small subset of them, with pre-set increments. I know this might sound quite illogical at first, but trust me - it’s really helpful!

Customization

So, Tailwind’s utility-first approach has many advantages, but what else does the framework provide? Well, undeniably vast and deep customization options. Tailwind allows you to configure most, if not all of its utilities within a single tailwind.config.js file.

Such a deep level of customization is important for multiple use-cases, with the main one being design systems creation. Tailwind gives you customization options that allow you to maintain the utilities’ versatility, while easily modifying their values to fit your custom style across the board.

Ease-of-use

I’ve already touched upon it when speaking about the convenience of the utility-first approach, but I’ll repeat myself as this is one of my favorite features of Tailwind. This library is extremely comfortable and easy to use. Don’t let yourself think that it’s too hard to learn because of all the utilities it gives you. The naming scheme is so convenient that once you get a grasp of it, you’ll know exactly how to use the entire library. And besides, there are extensions for many different IDEs and code editors (like VS Code) that provide you with helpful autocompletion capabilities.

About the naming scheme though. It’s arguably one of the most important parts of any heavy utility-based library, and Tailwind made it just right. p-{n} for padding, text-white for setting color, -{n} for using a certain value for the utility, and md: or hover: for handling breakpoints and different states of the element - all that is truly brilliant!

Down-sides

Surely, after reading all the advantages you might think that I’m positively biased towards Tailwind. Mind you that all you’ve just read is simply me describing my experiences with the library. But sadly a coin always comes with 2 sides and so, Tailwind isn’t without flaws.

Usage

While the whole concept of utility-first CSS sounds great on paper, it’s really quite rough in implementation. I mean just take a look at a slightly more complex use-case than the button we’ve covered earlier:

<div class="md:flex bg-white rounded-lg p-6">
  <img class="h-16 w-16 md:h-24 md:w-24 rounded-full mx-auto md:mx-0 md:mr-6" src="avatar.jpg" />
  <div class="text-center md:text-left">
    <h2 class="text-lg">Erin Lindford</h2>
    <div class="text-purple-500">Customer Support</div>
    <div class="text-gray-600">[email protected]</div>
    <div class="text-gray-600">(555) 765-4321</div>
  </div>
</div>

Do you feel what I feel? Isn’t the HTML snippet here getting a little… crowded? This example is taken from Tailwind’s landing page, and even after looking at it for a short moment, you start to get this awkward, uncomfortable feeling. It’s unavoidable - the more utilities you use, the less enjoyable, and potentially even readable your HTML/JSX/Vue template/whatever becomes.

Tooling

Apparently, the Tailwind team is aware of this issue, as the framework does provide a solution in the form of custom directives. Here’s an example for the outer-most element from the previous example:

.container {
  @apply bg-white rounded-lg p-6;
  @screen md {
    @apply flex;
  }
}

Here we basically turn the previous use of Tailwind utilities into a dedicated CSS class, that’s composed of the same utils. To make that happen, Tailwind provides custom directives, like @apply (for applying Tailwind utilities to another class name) and @screen (for interacting with Tailwind’s breakpoints as both hover: and md:-like utilities are not available in this syntax), which we use above.

So what’s the issue here? Well, with custom directives comes the use of custom processors, and with that comes some additional setup. Now, it’s not like processing code for additional features is something bad, it’s just that I personally try to stay away from such means when it comes to CSS. Call me old-fashioned, but I’ve got enough processing going on the JavaScript side already.

I understand that tools like PostCSS with its Autoprefixer or postcss-preset-env are really useful when writing modern CSS. But that’s a bit different from introducing new directives to your code - directives that are specific to and only work with a given tool. This drastically limits the “portability” of your CSS and makes any potential changes of underlying framework or library much more difficult.

But let’s say that you’re willing to go with the crowded HTML, only to not use any pre-processing tools. Well, in this case, you’re still out of luck, as you most likely would want to do at least some processing to shrink the giant 144 KB size of Tailwind. Of course, it’s hard to expect a small size from a library of this kind, but it’s the CSS processing requirement that’s the real issue for me.

Customization

I’ve already mentioned all the customization options of Tailwind as its advantage, but sadly, it’s kind of a double-edged sword.

Sure, all these options are great to have if you’re willing to take some time to really create your own design system from ground-up. But arguably, it’s not what most people are going to do and it’s the defaults with only small tweaks that they’ll be relying upon. And that’s where all this customization hurts the most. The sheer amount of all the options, plugins, and variants can be really daunting or overwhelming for both beginners as well as more advanced Tailwind users. Of course, nothing prevents them from using the defaults without any configuration what-so-ever, but I think you get the point.

Tailwind is not the only tool that suffers from the need to find a balance between customizability and convenience. It’s like a guessing game - you’re never sure if you’re going to win.

Prototope

So, overall, I’ve got pretty mixed feelings on Tailwind. On one hand, I appreciate the utility-first design, but on the other, I don’t like the way it looks in the HTML file nor how it can be integrated into CSS with custom directives. That’s why I ended up not using Tailwind in any of my bigger projects but was inspired to create my own library instead - Prototope.

Utility-first CSS-in-JS

Prototope is a utility-first CSS-in-JS library, created specifically to go alongside my UI library - Isotope. It’s heavily-inspired by Tailwind’s utility naming scheme and overall design, with a difference of it being a JS instead of a CSS library.

import { bgColor, h, w } from "@isotope/prototope";
import { createDOMView } from "@isotope/core";

const view = createDOMView(document.body);
const { node } = view.$(Prototope());

node.div([bgColor("primary"), h(8), w(8)]);

All of Prototope’s utils are essentially Isotope directives - functions that can modify Isotope nodes they’re used on.

After initializing Prototope with a single top-level Prototope() component, you can use all of its utilities just like that. Isotope nodes accept arrays of directives and so you can easily combine, merge, and operate on your custom utility sets the way you want.

There’s also support for breakpoints and element variants - just like in Tailwind!

import { bgColor, hover, h, w } from "@isotope/prototope";

// ...

node.div([
  bgColor("primary"),
  hover(bgColor("secondary")),
  h(8),
  w(8),
]);

Instead of dashed names, Prototope accepts config values for certain utils in the form of simple function parameters.

Behind the scenes

Now, Prototope works a little bit different than Tailwind, in a sense that it applies its classes at runtime, through JS. And the way it does so is also different. Instead of applying multiple classes to an Element, it applies only a single one, with a hashed name, and then sets all the styles on it. Sort-of like inline styles, but with support for @media and :hover-like rules.

And of course, there’s a server-side implementation too, for those of you who are wondering.

Interested?

Prototope still doesn’t solve all of the utility-first CSS problems. And yet, it’s something I recommend you to try if you’re into CSS-in-JS and want to feel how it works with the Tailwind-like approach. If you’re interested in it and Isotope, definitely go check out the docs, the repo, and feel free to play with it on your own!

Bottom line

So, this is just my opinion on Tailwind. Like I’ve said, I really like what it’s doing, but it still has some major drawbacks. With Prototope, I wanted to fix a few of them and make a similar library that’s a bit more fit for my personal type of use. If you find it interesting for you too, I encourage you to check it out.

Anyway, I’m interested to see your thoughts about both Tailwind and Prototope down in the comments below! If you’re interested in more up-to-date web development content, feel free to follow me on Twitter, Facebook, or through my newsletter. Thanks for checking in!

Sponsored links - check them out for more Web Dev content!

If you need

Custom Web App

I can help you get your next project, from idea to reality.

© 2024 Arek Nawo Ideas