6

Tailwind isn't the answer

 2 years ago
source link: https://dev.to/madeleineostoja/tailwind-isnt-the-answer-2opj
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Cover image for Tailwind isn't the answer
Madi Ostoja

Posted on Oct 24

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 modeExit 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 modeExit 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 modeExit 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 modeExit 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 modeExit 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, with real CSS, without all these new problems?

Introducing Pollen

Pollen is a new CSS library that does exactly that. Inspired by Tailwind, it takes its best ideas and implements them as a micro-library of CSS variables. With a 1kb core that can be used anywhere, without a buildstep or class naming conventions, it gives us all of the biggest benefits of Tailwind without reinventing how we write CSS.

Write CSS however you want, with regular pseudo selectors, media queries, and all the rest, and abstract out every value choice into a single source of truth.

Extend it into your own design system with plain old CSS. Modify it on the fly with JavaScript without any runtime library. Make it responsive without new concepts or tools.

But you might not even need it

Full disclosure: I created Pollen. But I'm not trying to sell you on using it. I'm trying to sell you on the ideas behind it. If you already have a solid design system with sizes, typesets, colours, and all the other shared values of an interface defined, then you don't need Pollen, and you certainly don't need Tailwind. Write them as CSS variables in one place, 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, but they are made harder by the new DSL it tries to introduce. 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.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK