DEV Community

Cover image for Tailwind isn't the answer
Madi Ostoja
Madi Ostoja

Posted on • Updated on

Tailwind isn't the answer

Tailwind CSS has taken the frontend development world by storm over the last few years. A utility-first library of CSS classes, it promises a new way of styling that's more consistent, maintainable, and faster than writing CSS directly. And for the most part, it delivers on that promise.

By using Tailwind you're almost guaranteed a single source of truth for all the values you use throughout a project. From typesets to spacing to colours, everything is defined in a single place. Which means that your code stays consistent and you aren't making things up as you go.

This was Tailwind's biggest idea, and the greatest benefit of utility-first CSS as a concept: compose don't create.

Tailwind achieves this with an extensive library of CSS classes to style everything. The idea being that you no longer write any CSS of your own, you compose predefined classes like lego pieces for every single property.

Developers new to this way of working often have a knee-jerk reaction just from looking at example code.

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

There's no denying that Tailwind is hideous. Its creator acknowledges as much right on the project home page. But that's just pedantry, and if the Tailwind way of doing things really was the panacea to all our problems then it would be a very small price to pay.

The Problem

The problem with this approach isn't that its ugly, or bloated (Tailwind purges unused classes), or that "you might as well write inline styles" (you shouldn't). It's that in order to apply a consistent set of values with classes, you also have to create classes for every conceivable set of rule:value pairs in CSS, even where it adds no value at all. So you end up using classes like .block rather than writing display: block and .text-center rather than text-align: center.

Of course you can mix Tailwind's more useful classes with regular CSS. But then you're breaking the Tailwind style-by-classes abstraction, and you have to maintain two seperate styling touchpoints for every element.

"So what?" you might ask, what's wrong with just using those classes rather than CSS? It certainly saves some keystrokes. Here is where Tailwind introduces new problems it shouldn't have to solve in the first place.

Reinventing CSS

Tailwind has to reinvent everything regular CSS can already do. Media queries, pseudo elements, selectors, and states. All of it now has to fit into the classes-only paradigm.

Tailwind achieves this with what it calls modifiers. By prepending Tailwind classes with md: they will only apply above the md breakpoint. By appending hover: a class will be applied in a :hover state. And so on.

Each of these tools is a poor facsimile of the functionality gaps it has to fill. Want an :nth-child or ~ sibling selector? Back to CSS. Want to target the devices between two breakpoints? Back to CSS. Want to target children of an element? Back to CSS. You get the picture.

Of course you can go back to CSS to do any of these things. Lovingly coined "bailwind", almost every project will need at least a little custom CSS when Taiwind's classes and modifiers just don't cut it. But then you're back at breaking the Tailwind abstraction, and giving yourself maintenance headaches.

And if this is already a given, then why use pointless classes like block when it adds no consistency or maintainability value over writing display: block in CSS, other than a few saved keystrokes? Because while gap-filling classes like this don't add value, they do add a new Domain Specific Language (DSL) to learn on top of the CSS we all already know.

Class soup

The thing every critic of Tailwind yells at first, its enormous class strings. Yes, they're ugly, but who cares. The problem isn't a surface-level developer perfectionism one. It again comes back to modifiers.

Every rule that applies to a modified state needs its own class with its own modifier. Unlike in CSS where these states and pseudo elements are naturally organised into logical blocks, Tailwind's modified classes can very quickly become a huge, difficult to maintain mess that has to be carefully teased apart line by line.

Take a contrived example of the button we had in the intro of this article, with an icon of some sort added to a ::before pseudo element, and less-than-ideal attention given to class ordering.

<button class="relative before:absolute bg-blue-500 hover:bg-blue-700 text-white before:left-2 font-bold before:text-sm py-2 px-6 rounded  before:top-1/2 before:content-['\f00c']  before:-translate-y-1/2">
  Button with icon
</button>
Enter fullscreen mode Exit fullscreen mode

Of course in this particular example the icon would be better placed as a real element inside the button, but the point stands. Without (and even with) careful ordering of classes, these jumbles very quickly become a maintenance nightmare.

JIT to the rescue?

Tailwind's new Just In Time mode compiles just the classes you use on the fly, rather than pruning back a goliathan stylesheet after the fact. It allows you to use modifiers everywhere out of the box, and most importantly write arbitrary values right in Tailwind classes, like margin-[100px].

This is another language feature that was added to Tailwind's style-by-classes DSL in order to fix problems it introduced itself. And while arbitrary values mean you don't have to break out of Tailwind's paradigm as often, they also diminish the core value that Tailwind provides β€” a single source of truth for a whole project. Taken to its logical extreme Tailwind JIT is really just reinventing CSS, bit by bit.

The Solution

As I said at the very beginning, Tailwinds' central idea is a very good one β€” a low-level, utility-driven design system to get rid of magic numbers and bring consistency to your CSS. The problem was the implementation.

Thankfully CSS now has the same solution as every other language to consistent values: variables. CSS variables, or more properly CSS custom properties, are fairly new to the language but already adopted by every major browser, and used extensively in Tailwind's own internals.

For example, Tailwind's .p-4 padding utility could be rewritten like this

:root {
--p-4: 16px;
}

.button {
  padding: var(--p-4);
}
Enter fullscreen mode Exit fullscreen mode

And since we no longer have to write separate classes for every rule:value pair, we can greatly simplify our utility-first design system. We could have one set of size variables that can be applied to any part of padding, margin, width, height, position, etc. Without needing separate utilities for every combination of every property.

:root {
  --size-2: 8px;
  --size-4: 16px;
}

.button {
  padding: var(--size-2) var(--size-4);
  margin: var(--size-4) 0;
}
Enter fullscreen mode Exit fullscreen mode

And since variables are part of the platform, they have a native runtime. We can interact with CSS variables using Javascript, and update them dynamically. This makes things like reskinning a whole interface for dark mode possible with just a couple lines of code, without introducing any new utilities or tools.

function enableDarkMode() {
  document.documentElement.style.setProperty(`--color-background`, `black`);
  document.documentElement.style.setProperty(`--color-text`, `white`);
}
Enter fullscreen mode Exit fullscreen mode

So why don't we, instead of reinventing the styling paradigm altogether, just abstract all the values in an interface into a single source of truth by putting them in CSS variables that can be used anywhere, in regular old CSS, without all these new problems?

By Example

GitHub logo peppercornstudio / pollen

Utility-first CSS for the future

Pollen


Pollen

Utility-first CSS for the future

Introduction

Version Size

Pollen is a standards-driven alternative to Tailwind. A micro-library of CSS variables, it encourages consistency, maintainability, and rapid development. Use it from prototype to production, as a utility-first foundation for your own design system.

What it looks like

Pollen's low-level variables can be used to build any design. They work anywhere and don't require a buildstep or class naming conventions. They're easy to extend and globally responsive, without introducing preprocessors or new syntax.

.button {
  font-size: var(--scale-00);
  font-weight: var(--font-medium);
  padding: var(--size-2) var(--size-3);
  background: var(--color-blue);
  border-radius: var(--radius-sm);
  box-shadow: var(--elevation-2);
  color: white;
}
Enter fullscreen mode Exit fullscreen mode

Documentation

Read the full documentation at pollen.style

Pollen is a standards-driven CSS library that came from this line of thinking. It takes Tailwind’s core ideas and reimplements them as a 0.85kb collection of plain CSS variables. It can deliver all the key benefits of Tailwind without reinventing how we write CSS, thanks to the tools we already have in the web platform. Since it’s just plain CSS it can be used anywhere in any context and in any way.

But you might not need it

Full disclosure: I wrote Pollen. But I'm not trying to sell you on adopting it. I'm trying to sell you on the ideas behind it for your own work.

Pollen is just a collection of hopefully useful CSS variables, translated from Tailwind. But If you already have a solid design system with sizes, typesets, colours, and the other shared values of an interface, then you probably don't need Pollen, and you certainly don't need Tailwind. Write them in one place as CSS variables, and use them everywhere. That's the way out of this insanity.

Bring consistency to CSS by getting rid of magic numbers with variables. The other problems of CSS (deep composition, leaky inheritance, performance optimisation) aren't solved by Tailwind, nor by CSS variables. But they are made harder by the new DSL Tailwind introduces. At least by sticking to regular CSS you have all the other patterns and tools we as a community have been working on for the last decade at your disposal, without any gotchas.

Latest comments (110)

The discussion has been locked. New comments can't be added.
Comments have gotten pretty spicy, and I don't think much valuable discussion is happening any more. See my comment for reiteration on some of the concerns raised.
Collapse
 
madeleineostoja profile image
Madi Ostoja • Edited

Hey guys, this has gotten a lot spicier than I anticipated, so I'm going to go ahead and lock comments on it.

A few things to reiterate before I do:

  • Tailwind is a solid library written by smart people, with great ideas behind it. I think the way these ideas are implemented introduces serious problems at scale, which often go unmentioned. If those problems aren't deal breakers for you, then by all means use Tailwind!

  • The other library I talked about, Pollen, is just a collection of CSS variables. It shows how Tailwind's key benefits can be accomplished using the tools we already have in the platform. I hoped that some people might find it useful, I know I have. But as I mentioned in the article, you absolutely don't need to use it, and I'm not trying to sell you on switching to it.

Apologies to anyone this article offended. That wasn't the intention

β€” madi

Collapse
 
rolandixor profile image
Roland Taylor

I agreed with some of the criticisms of Tailwind (though I wrote my own library/framework, Passionfruit, for a similar purpose and in a similar manner, long before I knew anything about Tailwind (probably before it existed, but I haven't checked)).

However, I wasn't expecting this to be a run-in to advertise another library. I feel like this should've been stated clearly from the beginning. Just feels dishonest, imo.

Collapse
 
andrewnatoli profile image
Andrew Natoli

Stuffing CSS variables into a tailwind config file seems to be a popular route, so cheers for implementing the principles of Tailwind with a CSS-first approach; not requiring config files and build tools to get there.

Before I clicked into Pollen’s docs I was looking for your take on Tailwind’s @apply but realize now you don’t need to mention that since your point is about configuring utility classes through JavaScript vs configuring them through CSS.

Great to have the alternative way to do things πŸ‘

Collapse
 
regislutter profile image
RΓ©gis LUTTER • Edited

I don't really see how using CSS variables is better.
From your example :

button {
   all: unset;
   font-family: var(--font-sans);
   font-size: var(--scale-00);
   font-weight: var(--font-medium); 
   line-height: var(--line-none);
   padding: var(--size-3) var(--size-5);
   background: var(--color-blue);
   border-radius: var(--radius-xs);
   color: white;
}
Enter fullscreen mode Exit fullscreen mode

You can do that in Tailwind with @apply like :

button {
  @apply font-sans text-xs font-medium py-3 px-5 bg-blue-100 rounded rounded-xs text-white;
}
Enter fullscreen mode Exit fullscreen mode

There is clearly no advantage to the variables.

Also, Tailwind is really useful to work with designers (and with Figma for example). They use a Tailwind template and you can clearly see the padding they want, you have the color and hue gradient applied, the text size, etc. You just have to type the Tailwind class corresponding.

BEM + Tailwind is for me really versatile. And if I need an extra responsive margin on an element just one time, I can do it in no time (exemple class="button mt-2 md:ml-2 md:mt-0")

[Edit] :
And totally forgot about useful classes like divide-x divide-y space-x space-y dark: ring-

Collapse
 
jakarlse88 profile image
Jon Karlsen

I haven't used Tailwind, but enjoyed this article nonetheless. I'll keep its arguments in mind, and if I ever consider using Tailwind I'll evaluate said arguments based on their merits and drawbacks in relation to my specific use-caseβ€”as one part of several in a broader evaluation.

I also appreciate that the author based on an analysis of a perceived problem came up with an example solution (which is pretty much our collective job description). I think that's a commendable mindset, no matter what you think of the solution specifically. It's a shame that what could have been a quite fruitful mutual discussion has degraded to schoolyard antics, here in the comments.

Collapse
 
anubra266 profile image
Abraham

@madeleineostoja I beg you, let this not be your last article, hate comments just mean you had something really worth sharing. You wrote this perfectly well, this is basically the model for a good research work. Bringing light to both advantages and disadvantages, your findings, then you ended it with your solution. Come on, that's how it's to be done. The people who really deserve to criticize the shortcomings of something, are those that are able to produce a better way of going about it, and that's exaclty what you did.

Thanks for the great write up, and please keep it up.

Collapse
 
hectorromo profile image
Hector Romo • Edited

For all who thinks this is a way to sell in Pollen, read the article:

Full disclosure: I wrote Pollen. But I'm not trying to sell you on adopting it. I'm trying to sell you on the ideas behind it for your own work.

Good points and valid arguments in my point of view.

Collapse
 
nnowwakk profile image
nnowwakk

I'm not saying Tailwind CSS is a magical solution to everything but all of your examples can be done "the Tailwind way" as well.

Each of these tools is a poor facsimile of the functionality gaps it has to fill. Want an :nth-child or ~ sibling selector? Back to CSS.

Tailwind has selectors for :first-child, :last-child, :odd-child and :even-child. Those cover most of the use cases but in case you want a class only on the 7th-child, the easiest way is to just put the class on that element instead of using the pseudo-selector on the parent element. Or if you are using a front-end framework to dynamically add the elements, you can add the logic of adding the class to the 7th child in javascript.

For siblings, usually you'd just add the classes to the element itself but you could also use the peer-* variants if you need to depend on a sibling element's state.

Want to target the devices between two breakpoints? Back to CSS.

I'm not completely sure what you mean with this but if you only want a class to be applied to let's say the md: breakpoint then you can just set the style back to the original on lg:. For example bg-black md:bg-red-500 lg:bg-black is red only if the window is 768 - 1024px wide.

Want to target children of an element? Back to CSS. You get the picture.

The whole idea in Tailwind is to apply the styles for each element itself so ideally you'd add the class to the child element. If that's not possible then you can create a selector for it yourself or use tailwindcss-children -plugin for example.

Collapse
 
mollycobb profile image
Molly Cobb

Cool...

Collapse
 
alexkarpen profile image
alexkarpen

Tailwind is the answer to huge projects, read the customizing section in it's documentation it will solve many of your problems.

Collapse
 
kozakrisz profile image
Krisztian Kecskes

It is a good article. It is sad some fan boy try to resolve their soul and ego problems here in the comment section. You should write more article and don't worry about these kind of people. Keep it up! Your photos are awesome btw :-)

Collapse
 
madeleineostoja profile image
Madi Ostoja

Thank you so much!

Collapse
 
peterf2170 profile image
PeterF2170

Btw your menu drawer on pollen.style is not working on mobile. Maybe fix it by using tailwind.

Collapse
 
madeleineostoja profile image
Madi Ostoja

It’s just a site on gitbook, but hope that was fun to point out

Collapse
 
marcus-sa profile image
Marcus S. Abildskov

The integrity of this post is hilarious - you're bashing on a CSS framework you obviously don't know enough about, just to promote your own CSS framework πŸ€¦β€β™‚οΈ

Collapse
 
juli91 profile image
Juli (she/her)

She isn't bashing on anything, she literally points out the good qualities of Tailwind right in the beginning.

Collapse
 
filipslezak profile image
FilipSlezak

I agree 100%, tailwind is reinventing css in a bad way. It's inline css under some shorter names and in class instead of style -_-

Collapse
 
mnaderian profile image
Mahdi Naderian

My friend, I sense you miss one big point in here.
The reason why people used Bootstrap and now Tailwind CSS is not consistency, but rather getting away from the hell of CSS.

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