DEV Community

Cover image for My mixed feelings about Tailwind CSS
Arek Nawo
Arek Nawo

Posted on • Originally published at

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:

  class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
Enter fullscreen mode Exit fullscreen mode

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?


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?


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!


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.


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!


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.


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"></div>
    <div class="text-gray-600">(555) 765-4321</div>
Enter fullscreen mode Exit fullscreen mode

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.


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;
Enter fullscreen mode Exit fullscreen mode

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.


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.


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)]);
Enter fullscreen mode Exit fullscreen mode

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";

// ...

Enter fullscreen mode Exit fullscreen mode

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.


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 here on Thanks for checking in!

Top comments (6)

gregfletcher profile image
Greg Fletcher

TailwindCSS solves a lot of problems for a lot of developers. That's great! But I'm not so keen on using it because I prefer to just use custom CSS. But if I needed to reach for a library I'd probably go with Tailwind.

I get why it's helpful for many people. I really appreciate that. Personally, I'd prefer to know CSS deeply and just use my own styles. That way I can use raw css, scss, styled-components, etc.

I like how you went custom and made your own tool! To me, that's more interesting because there's the opportunity to allow customization of CSS before it reaches the .css file. Facebook has an interesting custom solution that compiles down to raw CSS but removes any clashing styles and dead css.

laptoptheone profile image

I am sorry that you are finding TWCSS a bit difficult to integrate with directives. But why would I want to use CSS in JS in the first place? TWCSS is a CSS in the first place, looking at your review one might think you feel the same for the SASS as well...TWCSS miht have its downsides for sure but it is certainly not a directive creation. I have been working with TWCSS for a long time now and did not find any major flaw, and to sum up it seems you haven't been using it very thouroughly to give a honest review. I mean even in the official tutorials creator suggests to create own directives so that the code is more readable, and to use it directly in the html for quick tweaks. And e.g. even if you have a long string of css classes in the html, once you are done with cssing you can extract it without any pain. I am not a TWCSS advocate, even if I have written 2 articles about TWCSS, but I did not read here any concrete downside.

davidteren profile image
David Teren

This is not so much a review as it is a hijacking of TWCSS to shamelessly promote his own framework. 🤦‍♂️

gregfletcher profile image
Greg Fletcher

I don't see that at all. The author pointed out the good and bad things about TailwindCSS and had a custom solution. Nothing wrong with that! I love seeing people's creativity.

moopet profile image
Ben Sinclair

Instead of repetitively writing color: white in multiple classes, you just drop the text-white class and that's it!

That's what the cascade is for! The big C in CSS that everyone seems to be trying to get away from these days. You have something like an aside that is styled as a sidebar, and you give it colours and so on, and elements within it inherit those.

Tailwind is another big push towards binding content and style.

Apparently, the Tailwind team is aware of this issue

Yes, I've seen people talk about "tooling" with Tailwind. As far as I can see it's an acceptable way of using it - in fact, it's the only acceptable way of using Tailwind as far as I'm concerned. Anything that wants me to add text-white margin-small to a redundant div is completely off the table.

So, tooling fixes that? It does... but it's a lot of work to get back to the original problem. Tooling in Tailwind is reinventing the wheel.

jwp profile image
John Peters

This concept is excellent. One step closer to turning Css Into a real language instead of a DSL with no real tooling. How does it rate with refactoring ability and finding duplicates?

Some comments may only be visible to logged-in visitors. Sign in to view all comments.