About once a year, if not more frequently, drama seems to pop up in the developer community surrounding Tailwind CSS, and it's always about the same thing: "Look how many class names are in your markup file! So messy! How can you read any of it!?".
Now, let's be fair - seeing a list of Tailwind classes upon first viewing can seem messy and overwhelming. CSS already has a reputation for being less-than-easy to learn, hard to memorise, and inconsistent it can be with regard to best practices.
However, the recent Tailwind drama is (as usual) a great example of how a fundamental misunderstanding of tools you're using can make them seem better or worse at doing the job they're advertised to do.
On the left, HTML and Tailwind a dev authored with the idea it would be easier to read.
— xirclebox (@xirclebox) June 27, 2023
VS.
On the right, I rewrote the markup using vanilla CSS classes. #webDev #frontEnd #CSS pic.twitter.com/8PoeFpccFY
The wrong end of the stick isn't always labelled
A few days ago, @xirclebox tweeted a screenshot of a side-by-side comparison between a TailwindCSS HTML snippet, and the same snippet using vanilla CSS. Currently at 2.2m views and counting, the tweet has brought opinions from just about every developer I follow, and a couple thousand more, I'm sure.
There's no reason to beat around the bush here - the Tailwind snippet is much harder to read than the CSS one on two levels. First, the amount of Tailwind classes in some areas makes it really hard to get a mental model of what's going on in the snippet. Second, the amount of line breaks caused by all the classes makes it not significantly, but somewhat more challenging to read the actual HTML itself.
The indentation helps, but at first glance, I do get a much better idea of the makeup of this component when looking at the vanilla CSS snippet. However, it's important to note here that OP is using tw-
prefix (which tells us they're likely adding Tailwind to an existing code base with clashing class names), which will add 3 characters to each Tailwind class name.
There's a catch though - this isn't at all how Tailwind is meant to be used.
Comparing Apples to Oranges
There are two kinds of missing the point with Tailwind, generally speaking. One seems a lot more like it's up to the maintainers to attend to - which is using Tailwind for the first time, in an existing project already making use of some CSS paradigm. This is normal when working with new tools, and in some ways can be helpful to the maintainers in understanding how better to educate first-time users.
On the other hand, however, we've got misrepresentation. It's impossible to say which is which, when we have no prior knowledge of the person making claims and have no idea of their exposure to Tailwind, let alone CSS itself.
I think this sort of misrepresentation could be captured by turning the tables and trying to make a similar claim about CSS being too verbose compared to Tailwind:
// Vanilla CSS - Messy, Unreadable, Unclear
<div style="background: #ffe7e8; border: 2px solid #e66465;">
<p style="margin: 15px; line-height: 1.5; text-align: center;">
Well, I am the slime from your video<br />
Oozin' along on your livin' room floor.
</p>
</div>
// TailwindCSS - Clean, Readable, Concise
<div class="bg-orange-100 border-2 border-red-300">
<p class="m-4 text-center leading-6">
Well, I am the slime from your video<br />
Oozin' along on your livin' room floor.
</p>
</div>
But this seems misrepresentative, right? Because as much as it's possible, allowed, and even makes sense sometimes to write CSS in style tags, it's usually very impractical and the option of using external style sheets is a sound solution to this problem.
One might also say, "Well, didn't you have to look up the correct Tailwind classes to use?" - No, I used intellisense in my IDE like a developer living in the modern day. Tailwind has a great plugin that allows you to hover over your classes and see the direct CSS behind it, variable patterns and all, right next to the utility class name.
I don't think that @xcirclebox was intentionally misrepresenting Tailwind in the slightest. I think they're trying their best to try out a new tool that comes widely recommended - and didn't like the results. Which is honestly nothing more than a fair and free opinion, expressed well enough to engage many, many others in responding to the opinion.
Let's take a look at where they may have misstepped, or where Tailwind itself isn't really clear enough to steer new users in the right direction.
Like Lodash, but for Styles
To get back to basics, let's try and understand what problem Tailwind exists to solve. Let's take the lodash JS library for example. It's a collection of useful utility functions which, while could probably easily be written by most developers with a quick google, are a useful weapon to have in your arsenal.
Sure, perhaps their sortedLastIndexOf
isn't the proven fastest implementation of that function in Javascript. Or, perhaps it wasn't at first - but as the project grows to become the de-facto standard for JS utility functions, it seems likely that anyone else writing a faster sortedLastIndexOf
will either consider contributing their solution to this massive and widely used open-source project, or let that new snippet spread and proliferate until someone notices it's a better alternative to the one lodash supplies, and likely creates a pull request correspondingly.
There's something very ergonomic and useful about a collection of grounded, sensible and sane utilities which can be relied upon to be consistent, while still retaining opt-outs and the option to break things down a level.
Bringing this metaphor back to Tailwind - we can assume that as a 6+ year old, 70k star utility CSS framework, Tailwind likely has a set of consistent and reliable utilities that shouldn't need break-outs for most projects. Their own home page refers to itself as a "utility-first CSS framework"
It's also worth mentioning that at this point, Tailwind serves as a great reference for how things should be done in CSS. Their paradigms around common use cases like background images, grid / flexbox layout, and typographic styling are often implemented in a best-practices manner, and I've learned so much about CSS from hovering over my Tailwind classes and studying the underlying CSS, discovering reliable methods I return to whenever I use vanilla CSS in a project.
How to use TailwindCSS Properly
There's a lot wrong with @xirclebox's Tailwind snippet, but there's some right with it too. Let's look at how it could be improved in a few ways, using the sort of best practices the Tailwind maintainers encourage.
Separation of Concerns in HTML and CSS
This term is thrown around a lot when it comes to CSS and HTML. However, I don't think it makes sense to talk about HTML and CSS as 'separate concerns'. When the order of your <div/>
's dictates the order in which they're visually displayed on the page, how can we really say that the code we're using to instruct the layout and visual display of these elements should be hidden away in another file?
Being able to glance at your component and move elements and styles around in the same actions is something we just can't do using an external CSS file. Additionally, by either omitting or including classes based on their need (rather than editing classes themselves to update appearances), we can be sure that changing the style of one element won't affect others.
Say I have the following simple class for a button style:
<style>
.btn {
color: "#fff";
}
</style>
And this style is applied to two buttons
<button class="btn">Login</button> // Should be white
<button class="btn">Logout</button> // Should be red
I now either need to create a whole new class, or break out into some sort of SCSS pre-processor to nest my classes in a nice, clean, BEM format:
<style>
.btn {
&__white {
color: "#fff";
}
&__red {
color: "red";
}
}
</style>
<button class="btn__white">Login</button>
<button class="btn__red">Logout</button>
With Tailwind however, I just change the word white
to red
:
// Before
<button class="text-white">Login</button>
<button class="text-white">Logout</button>
// After
<button class="text-white">Login</button>
<button class="text-red">Logout</button>
// 🧠Big Brain: Set default button colour in parent
<button>Login</button>
<button class="text-red">Logout</button>
The meaning of each class name won't be immediately obvious to a new Tailwind user, but by the time you're regularly using it across your project you'll come to appreciate reading your markup and styles in one place - leading to an almost "cleaner" result of separation of concerns. That being said, most classes are sensibly named and can be understood from name alone. Again, intellisense helps a lot here.
Using Components
The biggest culprit in making Tailwind look bad, is that snippets like @xirclebox's don't really represent the recommended (and common) way developers implement Tailwind.
An improved version of his Tailwind snippet may look as simple as something like this:
<Carousel id="animation-carousel">
<CarouselIndicator />
<CarouselItem item={currentItem} />
<CarouselButton icon={<PrevIcon/>}>Previous<Button>
<CarouselButton icon={<NextIcon/>}>Next<Button>
</Carousel>
Or even simpler, just:
<Carousel id="animation-carousel" />
Both, I'd argue, are far more readable than any of either of @xirclebox's examples.
Of course, not everyone is using a framework that allows for this sort of thing. But, like it or not, that's primarily what Tailwind is made for - it was created, specifically as I understand, as a CSS utility framework that makes sense to use with components, since in 2017 the options for component-friendly CSS were much more varied, and less agreed up.
We still have a lot going on in our components though, right? Would our <CarouselButton />
component now contain all the markup we saw in the original snippet?
Good news, it won't! We've got components to rescue us again:
import BaseButton from "components/BaseButton";
import IconWrapper from "components/IconWrapper";
type Props = {
icon: React.ReactNode,
children: React.ReactNode,
};
export default function CarouselButton({ icon, children }: Props) {
return (
<BaseButton
styles="absolute top-0 left-0"
icon={<IconWrapper>{icon}</IconWrapper>}
text={children}
/>
);
}
We did a few things here which might not be immediately obvious - let's review:
- We extracted shared button styles to a shared base button component.
- We added in only the styles required for this button alone.
- We prepared a wrapper component to apply styles to whichever icon we pass in.
- At every step, we tried to think about where we may / may not re-use styles or component fragments.
It seems obvious, but due to the current discussion maybe it's not - Tailwind covers this as a basic principle in their TailwindCSS Core Concepts documentation. It's worth a read if you find yourself repeating styles, even as an experienced Tailwind user.
The Downsides, and Back-and-Forth of TailwindCSS
As much as the examples above seem like a great improvement on the original snippets, there's still the issue of having separate styles in separate files - although this time, with their related markup and at times, content. Speaking from experience, however, this is a very reasonable alternative to having to view entire groups of markup and styling separately.
When I want to change something about a <CarouselButton/>
, I'm entering that file prepared to edit the markup, styles, and possibly JS that affects either of those, aware that my power is now limited to the button. Consequently, I'm then editing the <Carousel/>
itself, which isn't much more than a layout wrapper, to make sure that my new button styles don't mess up my existing layout.
This practice, when done with intention and specificity, grows into a larger skill of defining re-usable components by default. Small notions like "padding should be applied from a parent, not from within a child", and asking yourself "do I want to pass just the text, or the text tag too?" make a big difference in designing components that just work when re-used across a code base - a principle which Tailwind prioritises over first being easy-on-the-eyes.
Again, there's always the opportunity for extra rules, abstraction componentized and one-offs in your globals.css
or tailwind.config.js
files - but again to speak from experience, if either of these files is hitting more than 100 lines, it's incredibly likely you're missing a key instruction from the docs, and making your own life more difficult needlessly.
At the end of the day, this is largely down to personal preference - but I strongly it's a personal preference between two ideal options - rather than a good implementation of one option, and a weak implementation of another. To go back to the lodash example, no one wants to use my one-liner sortedLastIndexOf
that's slower and always requires a review of usage due to lack of documentation.
In Conclusion
In general, I don't think it's a great example of how usable Tailwind is to post large HTML snippets with a multiple components in a row. It's almost never used this way, and the way in which it is used is a lot more malleable and customizable than it first appears without altering any of the library's defaults.
A good process to follow when Tailwind starts to become unreadable is to ask how the styles can be broken down and, more specifically, componentised - intrinsically tied to the components they're responsible for displaying.
Hopefully this discussion, as they always seem to, brings some wider awareness of how folks are using Tailwind in ways that aren't ideal. At the least, previous such drama has done so for me - especially around the usage of @apply...
nothing i am trying my best
— Apply (@Apply) June 28, 2023
Top comments (0)