Tailwind CSS has grown increasingly popular for its utility-first approach to styling HTML. While it offers several advantages, such as design uniformity and extensibility, it also has its set of challenges, including lack of abstraction and repetition. This essay aims to offer a balanced perspective on Tailwind CSS, providing insights that could guide your decision to use it in your projects.
Design Constraints
One of the most appealing features of Tailwind CSS is its design constraints. These constraints enforce a uniform look and feel across all components of a website.
Extensibility & Configuration
Tailwind provides a configuration file that offers incredible flexibility, allowing developers to create any number of CSS properties. It even supports Just-In-Time (JIT) compilation, enabling the use of arbitrary values directly in your markup, similar to inline CSS.
Preflight
Tailwind's Preflight is a robust normalization feature built on top of modern-normalize. It harmonizes default styles across different browsers and offers a multitude of other features, such as easy-to-set borders and a reset of browser styles. Preflight is so useful that I would consider using it even in projects that don't utilize Tailwind.
Lack of Abstraction
Tailwind CSS does not abstract away from the underlying CSS, often resulting in class names that aren't much better than using inline styles.
Repetition
The framework falls short when it comes to creating reusable components. As illustrated in the following code snippet, the utility classes can become quite verbose and repetitive.
<div class="mt-3 flex -space-x-2 overflow-hidden">
<img class="inline-block h-12 w-12 rounded-full ring-2 ring-white" src="https://images.unsplash.com/photo-1491528323818-fdd1faba62cc?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80" alt=""/>
<img class="inline-block h-12 w-12 rounded-full ring-2 ring-white" src="https://images.unsplash.com/photo-1550525811-e5869dd03032?ixlib=rb-1.2.1&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80" alt=""/>
<img class="inline-block h-12 w-12 rounded-full ring-2 ring-white" src="https://images.unsplash.com/photo-1500648767791-00dcc994a43e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2.25&w=256&h=256&q=80" alt=""/>
<img class="inline-block h-12 w-12 rounded-full ring-2 ring-white" src="https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80" alt=""/>
<img class="inline-block h-12 w-12 rounded-full ring-2 ring-white" src="https://images.unsplash.com/photo-1517365830460-955ce3ccd263?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80" alt=""/>
</div>
Tailwind does acknowledge this shortcoming but offers solutions that are not entirely satisfactory. You can either adopt another framework, which might not be desirable for everyone, or use their @apply
directive, which doesn't fundamentally solve the problem.
Note
Since writing this I've discovered that using Tailwind's plugin for prettier sorts valid tailwind classes, allowing repetitive classnames to be easily compressed when using gzip or brotli. This means that verbose class names have virtually zero impact on the end user or SEO.
Conclusion
Tailwind excels in rapid prototyping and in its capacity to adhere to a design system through extensibility. Its Preflight feature is notably excellent and could benefit more projects. However, it does struggle with verbose and sometimes unmaintainable class names, leading to inflated HTML file sizes.
Thanks for reading! If you take one thing away from this essay, consider incorporating modern-normalize into your future projects for more consistent cross-browser styles.
Top comments (25)
Iβd say that Tailwind CSS is definitely not my favourite CSS tool, but Iβd point two advantages of the tool anyways:
speed of development, since itβs a library of pre-defined classes and you can create designs quickly.
consistency, since it reduces the need of custom CSS styling.
Iβd say, the configuration is the worst part of it.
Still, CSS can be so powerful and so much more fun to work with, as long as you invest the right amount of time to it. There are far cooler tools to use like Styled Components, for example :)
you're right.
I was about to mention configuration. It's still something that bothers me on every project...
Disagree, Styled components feels like it's counter-intuitive and it's an unnecessary bloat.
Could you please elaborate on that? I mean, itβs not the easiest tool to learn either, but it does offer more reusability when you compare it to building CSS modules in a React project, but thatβs just my opinion. Would be keen on hearing about othersβ perspectives as well!
Styled components are 12.42kB min+gzip on base, so a bit large. I do like how it is similar to tailwind in that your styles aren't decoupled from html, and there is twin.macro for tailwind. I also think you should check out Stylify from @machy8, which is a cool hybrid of the two.
I understand your point and reasoning!
Thank you for sharing your thoughts and for the recommendations!π
I personally believe that traditional CSS is a lot better, it's not that hard, and it's more reliable.
Good post.
I agree with the general thoughts here, also feel like Lack of Abstraction is probably the most surprising delight here. The shallowness of the abstraction makes it such a good layer for so much work I've done with it.
I'd say my biggest agreement here is the repetition part here. Tailwind gives you a lot of rope to hang yourself with, and I think the right way to avoid problematic repetition is whatever you come up with yourself, Tailwind isn't helping much!
Hi @gravy59 ,
I have posted Best Practices for Utility-First CSS right now and saw your article.
I understand your points, but in the same time I disagree with you.
Even though I am the author of Stylify CSS, which is an alternative to Tailwind, I will defend the Utility-First CSS "base" here.
In my humble opinion, I think, that a lot of "bad things" about Utility-First CSS (Tailwind, Tachynos, others), simply come from "laziness" and the "simplicity of utilities usage" and a lot of them can be avoided by simply thinking things ahead, splitting the code and using the tool features correctly.
You can style any website that is using Tailwind with pure CSS, but let's face it, writing CSS isn't easy. It is for a small blog. But when the project starts to grow, you can get even 200+ css files. It's hard to maintain, clean, and compose together to get the correct bundle for the page, selector names often makes no sense and not even bem will help you, there are lot of duplicates, etc...
Every tool have its cons but utilities solve a lot of issues, not just fast prototyping and small size.
But I understand that not everyone likes to see a lot of classes in one attribute.
Totally agree. Tailwind "provides" a well thought out design system for free.
If you read this and you haven't read the "Refactoring ui" book by the creators of tailwind - i higly recommend it (first two chapters are free). However you are not limited at all and can use any arbitrary value and even css variables with the bracket syntax w-[420px] w-[var(--custom-width)].
It is built with customization in mind. Our designers are able to export design-tokens from figma. We then pipe them through style-dictionary and out comes a js file that you put into the tailwind config as a preset. The presets themselfs can take in other presets - so if you are building a whitelable solution for a product you just provide the brand specific theme by passing in the brand name via the .env. Inside your IDE you get autocompletion for all the values added to the config thanks to intellisense. You can choose to completly remove the defaults provided by tailwind or just add on to them. Usually the global variables named by the designers dont clash. So i end up with something like "w-space-2x" (space is holding our own custom scale).
i find this example to be very bad faith. If you build something like that imagelist you reach for reuseable components. even vanilla html offers that functionality
<template id="component-template">
<img "class="inline-block h-12 w-12 rounded-full ring-2 ring-white" alt=""/>
</template>
sure in this case you need javascript but even if you abstract the utility classes into your own class with @apply you still have the repetition of the html and now you dont have a single source of truth anymore.
I agree that their solutions to repetition are flimsy at bestβ¦ but they also highlight the fact it doesn't really solve the problem it claims to βas you point out.
If you need Svelte or Vue (or any form of templating) to factor repetitive patterns, this days it means you also have css scoping anyways. Meaning you can just name your stuff carelesslyβ¦ if you name it at all. I mean, I hardly use classes in Svelte, I just go straight for nodes π€· And then, you just jump the problem one level up: you don't name your css classes, but you have to name your components.
And then again, you build fast, but how maintainable is it? Want to change all spacing in cards. You look for
.card-spacing
? Of course not,Β you hunting formx-4
β¦ ho, unless that one that was anmx-2
with apx-2
and that one that looks like a card but is panel⦠"We should put names on things, no?"Great article, you got my follow, keep writing!
When I make simple HTML without any CSS, just enough to import tailwind cdnjs and that is enough for joy all advantage, for example: tailwind sticky test - I make one css for
<p>
. Much fare easiest solution, and I never need to think about class names.your example in react:
I would go one step further and extract the image into its own component that accaptes the src & alt.
My brain works best with backend development but I should say that Tailwind CSS is the only frawmework allowing me to build beautiful UIs. It's very logic in its approach and coupled to a framework like NextJS it feels like perfect to me.
Everything is a component, each component has inline CSS. For common things like titles, images, etc... I use the
@apply
directive. Works like a charm, the feedback loop is really fast and unless you have important size constraints I can't see how Tailwind CSS is an issue.But as I said, I'm more a backend developer than a frontend developer so not an expert at all!
Interesting post, @gravy59, but there are a few points that I feel need clarification or expansion to give everyone "the whole picture".
I originally started writing a comment, but it got a little long, so I moved it to a post instead:
TailwindCSS is great; why so much hate?
Dan Walsh γ» Feb 2 γ» 4 min read
Thank you! I appreciate your insight - I also love tailwind but I try to see both sides of an argument. I didn't realize you could gzip html!
I'm glad you enjoyed my post reply! I'm always a little worried it might be taken poorly. So glad we can have productive conversations on dev.to! π
Yep, gzip is awesome, but not something (as a frontend developer) you typically have to worry about, as your sever-level configuration should have gzip compression configured and enabled.
Not to mention their solution for repetition: use a framework... A lot of UI component frameworks make using tailwind obsolete.
Another major drawback of tailwind is that it does everything at compile time, making it impossible to access your configuration at runtime, something that css-in-js solutions do support. For example in emotion you can access the entire theme object at runtime. Because tailwind classes are made during compilation you also cant use any dynamic values. For example, if you have to make a white label app... You'd have to use css variables and manipulate those with your own custom JavaScript which is extremely obscure and sucks for maintenance.
You actually can access the config at runtime - just importt your tailwind config as an es module.
There's also twin.macro