If you work in the front-end, you've probably heard a lot about TailwindCSS, a CSS library, much like Bootstrap. Much unlike Bootstrap, however, Tailwind takes a different approach - it is all "utility classes".
And I am not a fan. I got a whiff of it and quickly learned the name is appropos: it was as welcome and useful as passed gas.
Before we start, let me try to explain what a utility class is. Let's say that you have many components, and many of them need to have the CSS style property: "display: flex;". Instead of writing that over and over in your css, multiple times, instead you create a class called "flex"
.flex {
display: flex;
}
Then, in every component that needs to be flexed, you add that "flex" class.
This is not a bad thing. I have written, and used utility classes a great deal myself, especially when I'm writing CSS without the aid of CSS-in-JS solutions or a preprocessor like Sass/SCSS.
What Tailwind does is take that concept to the extreme, with the idea being that you almost never have to write CSS, you just write different classes based on what styles you need to apply.
Which is an interesting choice, because...
This is just inline styles with extra steps.
That's it. Writing <div class="flex">foo</div>
has the same exact effect as writing <div style="display: flex;">foo</div>
. Well -- slightly different in that inline styles have higher priority than classes, but that's not really relevant in this context.
So - with that in mind, with the exception of CSS prioritization, any argument you could make against using inline styles in your codebase is also an argument against using Tailwind. For example: Lifewire: Avoiding Inline Styles for CSS Design. Or StackOverflow: What's so bad about inline CSS?. Or LogRocket: Why you shouldn’t use inline styling in production React apps.
I know it seems a bit lazy to rehash other users criticisms of inline styles to explain what's wrong with Tailwind, but it really is a 1-to-1 mapping. It's just inline styles with extra steps.
Some of the problems Tailwind shares with inline styles:
It's WET, not DRY.
When you want to change your site styling in a major way, if you've used utility classes, you need to go through each use of those utility classes - that is, every component - and visually determine what needs to be updated. For example, let's say that your company's primary color is blue. You'll have lots of blue stuff in your website, marked with things like: "text-blue-500" or "bg-blue-300" to determine different shades of blue. And that's fine until your company decides to rebrand, and all of the buttons - but only the buttons - on the site need to be red.
Now you have to go through each component and manually change "text-blue-500" to "text-red-500". And with 1000 edits comes 1000 oppertunities to introduce a bug. It is almost a textbook definition of why the DRY principle is in place.
Alternatively, if you're using regular-old CSS, what you probably did is create a class called ".button". You can just go into that class and change a single line: "background-color: 'red';". Any element that uses that class definition will now be red.
That brings us to the next point:
HTML should only concern itself with the structure of your page, not the styling of the page.
People talk about seperation of concerns a lot in development. CSS Modules (and especially .vue files) have done a lot to dispel the notion that you need to segregate structure, behavior, and style of the same basic building block of your site in seperate folders, but there is something to be said for seperating concerns. That is - each part of your code should be "loosely coupled and highly cohesive."
In other words, your HTML (structure syntax) shouldn't have information about what the styles should be, it should only contain information about the structure of the page.
Indeed, the ultimate reason for the invention of CSS, the whole point of the entire enterprise of CSS... was specifically so that you could seperate content from presentation.
And the method for doing this is through the "class" attribute.
The whole point of "class" is specifically that you can tell the computer what an element is - that is, describe an element's content. Once you've defined the content, then you just need to decide what content of that type should look like.
This not only means that you can go and change how an element looks without worrying about the underlying structure of the page, but also means that you can use these classes to describe what an element is. Indeed, part of the reason for BEM's naming syntax is that BEM names not only tell you what the component is, but also what it's relationship to other components in the document is.
Remember that when we write code, we write it for two audiences: the first is the computer itself, which doesn't care how the code looks so long as it runs, and the other is your fellow programmers. The easier it is for them to quickly identify what parts of your program are and how they interrelate, the more quickly that they can fix bugs, add features, and bring value to the organization.
Which brings us to:
It's hard to read
If you look at some HTML with Tailwind in it, you might say to yourself that the HTML looks "busy" or even "ugly." That's true, but it's missing the point.
Say what you will about inline styles, but they're at least providing enough context to let you know what's happening. Tailwind code is full of semantically obscure abbreviations; most of which are just redefinitions of already well known CSS properties.
Worse still, when they're not redefinitions, they can become downright cryptic. Tailwind prefers to use prefixed class names instead of media queries. Here's an example from Aleksandr Hovhannisyan
So this in Tailwind:
<div
class="w-16 h-16 rounded text-white bg-black py-1 px-2 m-1 text-sm md:w-32 md:h-32 md:rounded-md md:text-base lg:w-48 lg:h-48 lg:rounded-lg lg:text-lg"
>
Yikes.
</div>
could be expressed as:
<style>
.thing {
width: 16px;
height: 16px;
color: white;
background-color: black;
padding: 0.25rem 0.5rem;
margin: 0.25rem;
border-radius: 0.25rem;
font-size: 0.875rem;
line-height: 1.25rem;
}
@media screen and (min-width: 768px) {
.thing {
width: 32px;
height: 32px;
border-radius: 0.375rem;
font-size: 1rem;
line-height: 1.5rem;
}
}
@media screen and (min-width: 1024px) {
.thing {
width: 48px;
height: 48px;
border-radius: 0.5rem;
font-size: 1.125rem;
line-height: 1.75rem;
}
}
</style>
<div class="thing">Yikes.</div>
Now, the first example, I grant, is an awful lot less code to write, but look at how the second example is explictly defining height and width at specific breakpoints.
It is verbose - as raw CSS usually happens to be, but there are other solutions - such as Sass/SCSS, or solutions such as Emotion, Styled Components, etc. which allow you to use much more terse syntax without losing the cohesive meaning behind it.
Again, this is programmer 101. It's why senior developers get on junior developers for naming variables "const h = 10" instead of "const height = 10"
Another reason why the latter is easier to read than the former - Tailwind's classes are arranged horizontally, while the CSS is written vertically. The wider text is, the harder it is for a reader's eyes to jump to the next line, and the harder it is to find the one particular word you're looking for in a wall of horizontal text.
I bet your eyes started glazing over the second you saw the horizontal scroll bar on that Tailwind code sample, didn't they?
You lose a lot of the features built into standard CSS
I won't harp on this too much, but it should be pointed out that because Tailwind doesn't allow you to use the power of many of CSS's basic features. You can't chain selectors together, like so:
.foo:focus,
.foo:active,
.foo:hover {
/* css code */
}
You can't use combinators.
.foo p {
/* all p that are decendants of a .foo */
}
.foo > p {
/* all p that are direct children of a .foo */
}
.foo + p {
/* all p that are directly -after- a .foo */
}
.foo ~ p {
/* all p that are siblings of a .foo */
}
It solves a problem that doesn't exist.
One of the craziest things is that there's an obvious limitation to Tailwind's utility-class paradigm. What happens if you want to group related styles together? Rarely is "display:flex;" used without "justify-content: {value}", for example. CSS allows you to group these styles together into (wait for it), classes.
There's a tool for grouping related Tailwind classes together too. It's called @apply. It's special, non-standard syntax that goes in your CSS file (a directive) and allows you to string together a collection of tailwind classes and place them all under one class name.
That is to say, completely defeating the purpose behind the utility-class paradigm. If you end up having to use @apply, then *why don't you just use normal, ordinary, conventional CSS, which is easier to read, understand, modify, and doesn't require special tooling or parsing. CSS syntax can be complex, but it's been pretty stable since the late 90s, and isn't going to radically change anytime soon.
There's a very simple mental experiment I'd like to conduct with you.
Imagine a world in which CSS was never developed, but something akin to Tailwind was. That is, webpages could only be styled through repeating these individual class names... presumably through using table tags for layout. (To give you an idea of how old I am, I used to code web pages as a summer job in my junior year of high school in 1996 - and we used a LOT of table tags.)
If you could go from the limitations of Tailwind to CSS, wouldn't you consider that a quantum leap forward? Expressive syntax! Semantic naming! Style grouping! Selectors and combinators!. It would be like moving from Assembly to C for the first time. If so, why are we considering replacing CSS with something that does less, is more complex, creates bad quality codebases, and possibly ends up with massive vendor-lock in down the line?
If you want better than CSS, there are already solutions.
So a lot of the hype around Tailwind is that you can get rid of CSS. I know, everyone knows CSS can be hard to work with - especially if you have legacy codebases where the CSS wasn't written that well.
But for the most part, there are other, better improvements on CSS that actually do make styling simpler. There's the various CSS-in-JS solutions that allow you to leverage the power of Javascript to create dynamic class definitions; there's preprocessers such as Sass/SCSS/LESS; there's linters like Stylelint; there's best-practices methods like BEM/SMACSS. Is there overhead in learning these technologies? Yes. Is there tooling that needs to be part of your build chain? Yes. But unlike Tailwind, all of these solutions actively provide a tangible benefit to your code -- which is something that Tailwind can't claim.
It literally provides no value, and tons of problems.
At the end of the day, what do you get for all these problems? What are you left with? You're basically left with a less readable, more complex version of inline styles, a coding technique that we've been trying to breed out of junior developers for the past decade or so.
If you adopt Tailwind, it's going to provide problems for you and your team for years to come, and it's going to be hard to remove it.
Updates based on the comments section.
A few notes based on responses from the comments section.
Why trash something if you don't like it?
It's important to write about bad frameworks as much as it is to write about good ones, because of two reasons.
First, is the John Stewart Mill argument of "the value of the wrongful idea" - that in making a (good faith) argument for something incorrect, one arrives at a more correct, more complete view by analysis and refutation. Ideas must be continually challenged lest they go stale. Indeed - "one who doesn't understand one's opponent's arguments does not understand one's own" is a maxim I try to apply. When I wrote this article, I tried to look for the good in Tailwind. Why do people like it? (They don't have to write css. They can put style info in their HTML. They can write terser code. It gives them power to do things they don't know how to do in css.) Once I knew why people liked it, I had a much better understanding of why I didn't. (It combines content and presentation. It makes things harder to maintain. The syntax is obscure. You lose the power to do things that you can do in css.)
Second is that someone down the line is going to think: Hmm, should I add Tailwind to my app that has to be maintained by my team? And they're going to google "pros and cons of TailwindCSS". There will be plenty of articles explaining the pros. Here's one explaining the cons. Hopefully I've made a compelling argument not to use Tailwind so that future developers won't have to deal with it.
You're being disrespectful to the people who like Tailwind.
This isn't New Orleans Jazz.
I don't like New Orleans Jazz, so I don't have to listen to it. I don't buy New Orleans Jazz albums.
I am not in the habit of making detailed criticisms of what I feel to be the music compositional problems of New Orleans Jazz.
But I have never had a team lead, product owner, or stakeholder come up to me and say: "For the next project, I'm thinking that everyone on the team has to learn how to appreciate and play New Orleans Jazz."
Engineers and developers are often required to work with technology that they not only don't like, but which makes their work harder - often because decision makers either didn't care about the software's tradeoffs, or didn't know. Can't do much about the former, but we can do things about the latter.
When team leaders are thinking about incorporating a new technology into their tech stack, they should look for blog posts like this one to help them evaluate whether or not it's worth a try.
My thesis is not, as you seem to think, "I don't like Tailwind, and therefore YOU shouldn't like Tailwind either". That's a 12 year old's viewpoint of technology criticism.
Rather my thesis is: "If you choose Tailwind for a mission critical application, you will end up making your job harder, your application more brittle, and your team, in the long-term, will suffer."
But CSS has massive problems!
It really does. And there are better solutions than plain CSS. But Tailwind isn't one of them.
Say that in the 1990s, the only way to build a house was to bang nails in with a flat rock (CSS). And then, around the mid 2000s, a really smart guy invented "the hammer." (SCSS) It took adjusting, and you have to learn a new tool, but it did the job much better.
Around the early to mid 2010s, another guy invented the nail gun (CSS-in-JS). It did a lot of the same stuff as a hammer, but you had to know how to use it. There were tradeoffs, but generally, people who chose to work with hammers or with nail-guns usually ended up okay. Many peoplee would often use a manual hammer when the manual hammer seemed appropriate, and the nail gun when they seemed to need it. And all was good in the world of carpentry.
Then in 2017, someone came up to the carpenters and said: "Hey, see what happens when I do this!" and starts hammering in nails with the butt end of a loaded revolver (Tailwind).
And it's supporters quickly point out how more effective it is at building houses than banging in rocks.
"But it's a loaded gun. It might go off and shoot someone"
"Hasn't happened to me yet."
"Why don't you use a hammer? Or a nail gun?"
"I don't like hammers or nail guns."
"I can understand why you might not, but even if you used a rock, that would be safer in the long run."
"But using a rock is so difficult and inefficient."
"I'm not saying to use a rock. I'm saying that the hammer already solves the problems you have with the rock, but even the rock is a better tool for this than a loaded gun because it won't shoot anyone."
"But I love my gun."
"I guess it's alright if you use your gun on smaller projects on your own property, but..."
"Nope, I'm the foreman. Everyone on the site is using loaded guns from now on, because they're awesome."
Update: 9 May 2021 - Check out this blog post by Mykolas Mankevicius which attempts to rebut this article. I disagree, of course, but I think it adds to the debate, and if you're reading this deciding whether to use tailwind or not, you should hear what the "other side" of this issue has to say.
Agree but think my writing style might be too abrasive? Check out Benoît Rouleau's take on this article entitled Tailwind CSS might not be for you
Cher writes about some of the response this article has gotten and how it relates to our own unconcious bias in "Sexism, Racism, Toxic Positivity, and TailwindCSS"
Top comments (257)
Ah, if only there existed search and replace...
Or just create a custom color palette in your Tailwind config called 'brand', set your colors and be done with it. If brand colors change, update the color palette - easy.
Right, but branding is more than just color palettes. It's also - do we want the corners to be more round or less round on buttons. What if we want to have switch toggles instead of checkbox toggles? What if we want a specific page to look like a specific brand that isn't our main brand?
(This happened with a client of ours which sold Louis-Vitton gear -- Louis-Vitton wouldn't allow them to sell LV stuff on the client's page unless the pages that they landed on were branded with Luis-Vitton's color scheme).
Right, and Tailwind offers plenty of flexibility to manage these things via your config if required. It's not just a static set of a utility classes that cannot be altered without digging into the source - override the default border radius settings, or create your own presets for your brand if need be.
The exact use case in your article and comment (branded button) is mentioned in their documentation somewhere (I distinctly recall reading it), and I'm sure they suggest creating your own branded button class using @apply. If your branded button border radius requirements change, update the class, if the new border radius is outside the presets, then extend the config and then update the class.
If a specific client demands you brand their pages, then add a color palette for their brand, and make sure those components switch out
brand
forclient-brand
where required? Tailwind certainly doesn't stop you from using colors outside their default palette - I've only ever used the provided colors for non-brand accenting (e.g. greens for success, reds for errors, etc) and the grays for text/borders.In regards to moving to switch toggles from checkbox toggles, you're likely going to adjust the layout of the component somewhat, or perhaps add an additional container element ensure things are aligned correctly - I've encountered this exact type of scenario many times over the years - more often than not, it's more than just a few lines of CSS.
And if you're building a modern application, you'll likely have a checkbox component or at least a rudimentary template/partial to stay DRY - which will prevent the need to update every occurrence of that particular component across your application - again, something which I believe they touch on in their documentation.
To be frank, I wasn't a fan of Tailwind at first, and I certainly don't think it's the "be-all and end-all" solution - but after using it on a few projects I've come to love what it offers out of the box (especially the font, text sizing defaults, colors for accenting, gray set, grid, gradients, screen breakpoints, space-between, etc) with the ability to extend/override anything I desire if the case arises.
It's really allowed me (as a full stack developer building business applications) to start flowing without having to worry about any of aforementioned when I just want to get something clean, consistent and functional on the screen.
With all that being said, if you're creating basic web sites, landing pages, marketing pages - or complex applications without a component framework - Tailwind probably isn't the solution you want.
The problem is that the minute you start using @apply and using semantic classes again, you're moving outside of Tailwind's "utility classes" paradigm.
Now - while I could argue that "you might as well just write out the CSS" there is some value to alternative syntaxes. There's all sorts of tools for transpiling non-standard code into standard code. Typescript -> Javascript, Coffescript -> Javascript, ES2015+ -> Babel -> ES5, Sass -> CSS, SCSS -> CSS, LESS -> CSS, Knex -> SQL...
It would be an interesting -- and I think worthwhile! -- project that would use only @apply in CSS files and then transpile them (maybe with SCSS) into the appropriate CSS around the same time you're transpiling all those "$" prefixed variables, Sass functions, and "&" notation. If you want to have a tool that is designed to make
@apply px-5
shorthand forpadding-left: 5rem;
then that would be an interesting tool that wouldn't break semantic structure.padding-right: 5rem;
I've had the same thought - and it's essentially what I've been playing around with on a Svelte project with Tailwind's JIT mode. All the component markup, script and style tags are self contained in their own .svelte files - using @apply in classes within the style tag.
Certainly makes the mark up a lot easier to reason with when you have a lot of nested elements and many utility classes applied - I won't argue that fact.
The only issues I can see are:
1) Each components generated CSS classes are included in their own style tag within the head element, so I'm not sure what sort of performance issues this might cause if you have a lot of components.
2) Any class used with @apply within the svelte components, gets marked as "used" by Tailwind and ends up being compiled into the stylesheet loaded on the page. So if you don't use those utility classes in the mark up (and only in classes), they are essentially dead weight.
I'm sure neither issue would be difficult to resolve - either way, it's interesting none the less!
See, and this is the problem with writing a very opinionated article without understanding it. Tailwind is trying to get you away from using the @apply method. They have mention that in multiple tweets, articles, and videos.
Instead of @apply bg-blue-500 for your brand you can easily set a color theme of "brand" in your config then use your theme in the css. ie:
.button {
background-color: theme('bg-brand-500');
font-family: theme('fontFamily.brand');
///and any other variable you have set in your tailwind config.
}
then you just change your tailwind.config when you need to and you are done. Same as using a sass variable etc. It's not that hard and completely follows a dry principle.
How is this an improvement over CSS variables though? Why not var(--color-bg-brand-500) ?
A properly designed stylesheet tags an outer element with whatever the thing is. You then can assemble the object, and only light markup is needed for child elements because you can reach in with the stylesheet and control them. I had a job interview where they had "code standards" for doing css where you weren't allowed to nest tags to control them. Everything had to be top-level. Idiotic.
When you are writing code with frameworks, it's easy to use the framework to be absolutely consistent about how the children are constructed, so the markup can be very minimal. It makes it easy to read when the css tags are only used when they actually control some behavior, and they read semantically rather than as named inline styles.
It is more examples, that Tailwind will make your project doomed. It's very limited to strictly one style of UI and modifying it can be harder according to project design. Trust me, web development is not only greenfield project with fancy ui.
Sometimes you need reusable components. It's hard to make it with Tailwind.
He seems to blame the tool rather than using a better methodology for writing a better style
Color is not a brand, after all. And that could be a "hack". You don't try to win.
But that's just it.
Search and replace will only search and replace based on text strings. Which is great if the only place you use "text-blue-500" is in components you want to change.
That's the thing about utility classes - by definition, they're the same name everywhere you use them. So if you wanted to change "text-blue-500" to "text-red-500" in buttons -- and only buttons -- you'd have to find, and then manually look to see if it's a button before you replace it.
On the other hand, if you have a .button class, or even better, a .branded-button class, you can just change it in one place.
Find and Replace might be useful for changing variable names that are already fairly unique, such as from "my-button" to "branded-button" but it's not useful if you're trying to change, say, only some instances of "float-left" to "float-right".
But this is exactly how you should be using Tailwind anyway. If you are applying utility classes all over your html to make your buttons then this is akin to using inline styling. The power of tailwind is that you have an entire set of utility classes that allow you to make small tweaks on the fly because your designer wanted this specific button to have juuuuust a bit more margin-top and be fullwidth at certain resolutions, but you can also continue writing CSS as normal.
I see Tailwind as a utility. It's not there to replace CSS, but compliment your workflow so you can get on with building components.
Anyway, judging by your article and your replies in the comments you've made up your mind about Tailwind and that's ok, but there are many developers that are in favour of it, myself included. Best of luck!
I'm sure that you could use Tailwind that way, but at that point - why not just use small tweaks in inline styles instead of using Tailwind's classes? Inline styles also have the advantage of being higher CSS priority than class definitions, so you get exactly what you want right away.
But then again, the problem with "if you use it this way" arguments is that they're different from "if you use it as intended." You can see from the Tailwind's own documentation that the intended purpose is to replace CSS.
I'm not saying that utility classes aren't useful. I'm just saying that they shouldn't be used for everything. And they certainly shouldn't need all the tooling overhead that comes with tailwind.
I don't know why you keep saying this is not how Tailwind is intended to be used. The page you linked to is titled "Utility First". I infer that to mean the expectation is you start with utilities, and move to something else if you need it. Practically every word on that page seems to back up that inference.
Further down the page is a section titled "Why not just use inline styles?" which explains what they see as the advantages of utility classes over inline styles. You didn't even acknowledge their reasoning in your article.
The section after that one is "Maintainability concerns". This is where they expressly state that using apply to group styles together is, actually, using it "as intended".
Again, it seems clear to me Tailwind's intention is you would build styles using utility classes first. After some point, they completely expect you to group at least some of those utility classes into a "bigger" class. You seem to think this grouping completely negates the benefits of using utility classes, and that you might as well write it in CSS.
The documentation page that discusses this in particular is Extracting Components.
I'm always wary when somebody says a newer syntax is "hard to read". It's definitely possible to write code that is hard to read. But how can you say it's hard to read when you've spent years training your brain to parse some other syntax. Readibility is subjective with things like this.
For example, I found the example line in your article pretty easy to read. The purpose of
md:h-32
andlg:h-64
are actually more obvious to me than the media queries. But I do agree the long horizontal line harder to read. But you don't have to do it that way.I like your analogy of using single letter variables. But I don't think it's the same thing. These are just short forms of CSS properties. They are clear and documented.
I'm not sold on Tailwind myself. But if your conclusion is that it "provides no value", I don't think you argued it that well.
Eh... If that is your interpretation of Utility First, then I can't fault it.
It wasn't mine, for a number of reasons, but you may have caught me out on an assumption I didn't know I was making.
See, I thought that "Utility First" was referring to a coding philosophy, much like the philsophy of "Mobile First" -- you code for the mobile site first because the mobile site will always work on the full web-page, but not necessarily vice versa.
Mostly, though, the reason why I don't think your interpretation is correct (Start with utility classes, then move away from them) is because almost every example on Tailwind's site is about how you can convert from semantic classes and CSS/SCSS to Tailwind Utility classes. If anything, Tailwind seems to suggest that CSS is a pain point that needs to be resolved and that utility classes are the solution.
I agree that Tailwind might have use with rapid prototyping, but there's no real instruction on how to move from rapid prototyping to final product - You write Tailwind, you distribute Tailwind.
I remember a similar argument from the author of Clojure about how Clojure (and other Lisp-like languages) were "hard to read." He said: "I don't know how to read German, that doesn't mean that things written in German are hard to read."
But we can admit to ourselves that it is harder to learn certain languages than others, especially for an English speaker. And we can ask questions: Does this have a similar grammatical syntax? Does the language belong to the same family, does it have cognates and loan-words? Does it use the same sounds and tones? Is the alphabet the same?
In the case of programming languages, we can ask similar questions:
In this case, my criticism of Tailwind being "hard to read" deals primarily on the fact that the syntax is not expressive, and indeeds, chooses terseness over expressiveness. Back in the days of limited memory, sometimes a terse command was better than a long one, we still use "cd" for change-directory and "ls" for "list" in most Unix shells, but no such memory problems were at play here.
It's also hard to read because it's embedded inside HTML, listed horizontally, rather than vertically. Now I'm not saying that it would be as hard to read as if we did styles in PDoublePrime but compared to the default of regular CSS, it makes it hard to read. Additionally, since you're no longer adding your own semantic class names to your HTML, it can be hard to tell by looking at the source exactly what element in the HTML code you're actually looking at when you debug it.
Re: @tofraley
At the risk of taking sides (trying not to as I don’t understand TailwindCSS enough), I will say this: Usually when I encounter situations like this, I’ll bring it up to the designer. When that happens, at least in our case, it’s usually a misunderstanding (but not always). For example:
Only pointing this out since, on the development side, all too often I’ve found developers simply matching the comps precisely without first ensuring that the change was intentional and (in this case, accidentally) creating one offs that ultimately weren’t really intended. 😊
That's not true, you can use 'primary', 'secondary', etc.
tailwindcss.com/docs/customizing-c...
If you're relying on find/replace tools to make hundreds or thousands of edits to your codebase because of a change like this, it's still a problem. Everyone has seen (or caused) a scenario where a find/replace has gone wrong and replaced the wrong thing by accident, e.g. replacing "Arial" with "ArialBold", but accidentally creating instances of "ArialBoldBold" because your find-fu was off.
However, if the code was following DRY principles, there would be very few instances that needed to be changed, so would be far more likely to be handled better.
Not the point though. The point of CSS ,and/or SCSS for that matter, is too make styling more structured. If you need to do mass search/replace that might be a first cue.
Naming it primary, secondary ect. would be a way to go instead.
Search and replace still means you’re changing multiple things. More changes mean more chances of things being misaligned and it requires more testing. On an enterprise system, this increases the regression cost.
Damn... I wish we had:
Search and replace
The ability to use alias for colors, such as text-primary-500
The ability to abstract groups of tailwind classes into its own class. For example, with a keyword called, I don't know, @apply or something
Too bad we don't have any of those things and thus obviously by direct deductive argument Tailwind sucks...
@Ranieri Althoff, if you have never introduced an error via search-and-replace I salute you for your charmed life as a developer!
Use @apply and use same style everywhere. In react Vue we use components so we can just change style in one Component and it would work elsewhere.
if only all developers were careful enough to use search and replace responsibly
@rsa - I think you have summarized why using Tailwind is a bad idea. You have ended up creating CSS that describes the text color blue, not what you want your style for a particular element to look like.
There may be places where I want to continue using "text-blue-500" in other styles.
When you have a Button comp… Easy. Maybe variant:red is not a good approach. variant:primary
problem solved. works also for all other comps. Don’t see the problem here from the author.
We do we need constant values if we have search and replace...
Then don’t use functions at all
Lol I've been seeing hate articles with things that are non existent problems
And besides if you are using a js framework or in my case laravel components it's as easy as changing one file
The author was referring to the number of code changes.
With standard CC, you have to do the change in just one place.
The whole issue with the frontend community, in one comment. Well done.
Search and replace does not work all the time, as sometime you have to apply logic to it. Maybe you want to update only 763 elements.
In that case, you will still need to go one by one changing the class if you just put "button" in all of them.
You shouldn't. If the class is .button, and the .button class is only defined once, then one change to the .button class definition in your css should apply to all items that have the .button class.
I should have been clearer in my example by using something like ".branded-button" but you do see how you don't have to go in one by one, right? Because you're not changing what the class is named, only what the class does.
You often create a component for these scenarios, making different button components and reusing them. This pattern follows DRY too and also gives you the tooling and productivity of tailwind.
If only this was possible and such an easy change, you can't replace every instance of this in your app, what if it applies only to buttons/etc.
Yeah, because everybody want to use find/search every time when the want to change something in all places xD
You assume you want to replace all instances of text-blue-500
A bold article to write seeing as how it seems Tailwind is the starchild of CSS these days. But, an article I think needed to be written. I share most of your views on Tailwind.
I even tried to start a project from scratch using Tailwind because I thought "it's really popular and it looks really pretty - maybe I just need to buckle down and use it". It took me a few days until I came to your same conclusion - why don't I just use plain CSS (or in my case, SASS since I was already using it)?
It seemed silly to write:
when that's basically the same as:
and yeah - it's a lot less clear exactly what's happening. In my book, clarity always trumps cleverness.
The colour scheme is nice though, so I usually import it into my SASS files.
To someone more familiar with raw css, then that's going to be more clear. After using tailwind for a few days that syntax starts to be easy to understand. I'd argue actually much easier to know what happening than long lists of cryptic css commands.
A few:
w-full width: 100%;
w-screen width: 100vw;
container: The container class sets the max-width of an element to match the min-width of the current breakpoint.
How about a simple ring around a component?
Use 'ring'
In css: box-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color);
ring-inset --tw-ring-inset: inset;
All of this can be done using other libs and dependencies, but tailwind sure makes it easy.
Even the things that Tailwind "simplifies" are made more complex, and "ring" is a perfect example.
I'll admit, rings are difficult in CSS, which is why if I need to make one, I usually end up writing my own utility class for it.
But even looking at "ring" you end up having to add 5 classes or more to get your ring styled the way you want it. So "ring" ends up being "ring-3 ring-blue-300 ring-opacity-50 ring-offset-0 ring-offset-red-500" by the time you actually use it.
On the other hand, you could define all those into an actual utility CSS class, call it ".branded-ring" and just use that wherever you need it (instead of having that long string of five classes everywhere in your file.) Or even better, you could write branded-ring as a SCSS variable or a bit of styled component
css
`` code. If you use a CSS-in-JS solution, you could even have the color of the ring change according to props, giving you behavior control over style.And it would be more readable, more customizable, and then you don't have to worry about it. You would be able to write it once, and then your entire team could just import your code and reuse it - rather than every developer having to remember how to tailwind-style a ring every time they use that.
Of course, rings aren't that difficult to do, because Google is a thing that exists, and there are a dozen websites which explains how exactly to do it.
Of course, what happens when the CSS spec updates and rings are added due to popular demand? It happened with flexbox. It happened with css-grid. You never know.
You can use
@apply
, which does the exact same thing.It allows you to make a single class
.branded-ring
while still leveraging Tailwind's classes.You can do that in things like Sass, too. It's not a benefit of Tailwind.
But that's the point. It's not a good example of how using regular CSS (or pre processed CSS) is "better" in this case, it's not.
I'm not sure I follow. I'm saying that having the same feature as an existing build process doesn't make the new one better, it makes it the same. I'm saying that the claim is that BMW make better cars than Ford because BMWs have four wheels.
It's not a valuable claim in the original article.
He doesn't like/use Tailwind as he doesn't see the benefit over using what he already does.
That's fine and totally reasonable, but it's not an argument against Tailwind. His whole article is about how Tailwind is useless.
Maybe to him it is, fine, no one argues that. But that doesn't make Tailwind useless in general.
His examples are supposed to show how bad Tailwind is. It isn't. It just doesn't fit him or his way of writing CSS.
Cool, absolutely nothing wrong with that, but again that didn't make Tailwind useless or bad in any way.
lol same for that last thing
I am coming up with a better solution. Working on it now.
It's a criticism of an open source project, quite the opposite of a personal attack lol.
Go and read the full article he attacked people who use Tailwind and expressed judgements. There is difference between constructive criticism n personal attack. everything in this world has pros and cons he could not provide any rationale arguments you can critisize and bash everything and if you think people who are using are fools then make a solution better than Tailwind. Enough is enough he expressed his thoughts I expressed mine I dont know him you dont know me lets lave this.
@tanzimibthesam you can't on the one hand criticise Brian for writing an article against Tailwind and demand that he must provide a better solution before pointing out the problems with Tailwind, and then lambast him for doing exactly that.
In the field of development, we should regularly challenge the de-facto ways of doing things, and always be asking if the tool/method/etc is really the best thing to use in a given scenario. It's how the entire industry moves forward. We can question and bring forth discussion about things with articles and posts like this one. You don't have to agree with it, but it shouldn't warrant personal attacks or profanity.
He has to provide a better solution cause something is called personal criticism and one thing is called bashing and saying everyone is wrong who is using it.He is doing personal attacks and providing negative vibes. Stop defending this silly man. You dont know him and I dont know you or him neither nor he is providing a solution nor people are gonna stop using Tailwind.
He doesn't actually have to provide a better solution to anything if he wants to write a post pointing out what he feels are problems with that thing.
As for personal criticism, I can only go on what I saw on this whole thread. This involved him making some points that he felt were issues with Tailwind, and you throwing insults and profanity at him in replies. The only personal attacks I saw were from you. You're right in that I don't know him, I'm fairly impartial in this, although I do happen to agree with his criticisms of Tailwind.
As for him providing a solution (as I've said, he has no requirement to do this, it's akin to a non-driver pointing out problems with a bad motorist, or a non-artist highlighting parts of an ugly painting), he did actually mention he was working on something (and provided a link), for which you attacked him again. Those comments of yours appear to be deleted now, so I can't provide exact quotes, but I believe you did mention his proposed linked solution was a waste of time, bloated, and pointless.
Yup leave it man peace 😂
Not to re-open this, but I get a smug set of satisfaction from the fact that Airfoil has already been declared "bloated" despite the fact that A) it hasn't been written yet, B) the point of Airfoil is that you only really get value from about 9 of Tailwind's 300+ classes, so why not just write those nine in a way they can be reused and integrated?
I understand putting so much of your identity into, say, a political movement that when someone makes criticisms of the political movement - no matter how valid - you feel personally attacked. But I don't understand how you can do this with a css framework.
Amen
For us web dev isn't massive part of our business and kicking media queries into the dark abyss was satisfying when moving to tailwind.
A few things.
I wish you luck, always fun to learn something new and see how the community can alter development.
Interesting.
I would say that even if web dev isn't a massive part of your business, then why would you want to deal with Tailwind anyway? You're not really skipping out on the bulk of complexity of CSS, you're just writing CSS using an alternative syntax that maps 1-to-1 to actual CSS. It doesn't make it any easier.
Compare that to something like Bootstrap, which does make things like breakpoints, media queries, etc. easier. Yes, it's more opinionated, and "bootstrap sites look like bootstrap," but if web dev isn't a massive part of your business, then that's all you need.
And while you can compile tailwind into CSS classes (with either Sass, as you mention, or @apply) -- why would you want to? Why not just write the class directly, skip the middleman?
As for "container" - yes, it's easier to write 'container' than it is to fix max width at all media queries... the first time. But once you've done it once, it's easier to reuse. Now, if Tailwind were just a collection of commonly used CSS utility classes, I'd say that it has value, but "container" is a massive exception to the rule. 99% of tailwind is just one-to-one mappings of CSS properties.
In fact, I'd be surprised if someone hasn't written a very stripped down version of Tailwind that is just a single CSS file with "container" and one or two other cherry picked utilities. If they haven't, I might write it myself.
I work at a consultancy. The answer can be "very," depending on the client's whim. But aside from that, the main reason I might rewrite a style is because it's a bug fix, and in that case, I'd rather have to fix the bug in one place rather than fix it in 100.
Yes, but it's not like Tailwind invented the 760 grid system. If all you want is to avoid writing media queries everywhere, there are already tons of great libraries for doing that.
My problem isn't that there aren't some examples of good code out there in Tailwind. My problem is that it's 99% bad code, and the 1% of good code has been done, better, elsewhere.
The answer to virtually all of your complaints is extracted components.
Let's address your complaints one by one though:
It's WET, not DRY.
Not if you use extracted components.
You have a wrapper class you like to use a million times? Great. Extract it! Then you only have to edit it once to update everywhere. Still DRY my dude.
HTML should only concern itself with the structure of your page, not the styling of the page.
These are inextricably linked though. Ever use a modifier class to change how your component appears? Where is that going? In your HTML.
Know what modifier classes are eerily similar to? Utility classes.
For example: need to stop page scrolling when a modal is open? Rather than writing a new class to do that, just use the
overflow-hidden
class already at your disposal.It's hard to read.
I totally agree! ...So use extracted components.
Now you can write your html with a simple class like
card
, and then go to town in your CSS with:You lose a lot of the features built into standard CSS
Not true. Again, use extracted components.
It solves a problem that doesn't exist
At this point it should be obvious the problem it's solving, but just to clarify: it makes writing CSS easier and modifying your components in a logical way. Meaning, less time writing out individual css properties and values.
You want to see an obvious problem it solves?
I want a box to fill it's container, according to you I should just write plain CSS like so:
5 lines of CSS I have to write or copy/paste from somewhere. And if I want to update the positioning to be
1rem
in on each side? Now I have to update 4 lines individually.Not with extracted components:
If you want better than CSS, there are already solutions.
All of the solutions you mentioned don't make writing CSS any easier. You mention CSS-in-JS solutions, but that goes against your argument that styling shouldn't be in your HTML and you mention BEM/SMACSS but that's just a writing convention.
It literally provides no value, and tons of problems.
There is massive value in efficiency. If you want to write code faster and keep your stylesheets lean, there is plenty of value in something like Tailwind.
And I say this as someone who up until 6 months ago, refused to try out utility classes for similar hesitations. But instead of writing a big long post about why I think it's useless, I gave it a try and found a way to fit it into my workflow in a way that has allowed me to speed up my development time and still create accessible codebases for new developers (extracted components still live alongside regular CSS).
BONUS: nowhere did you mention the treeshaking capabilities of tailwind. If you're managing large-scale sites with massive style guides and you care about bundlesize, that alone is a benefit of Tailwind that I'd find it hard for anyone to argue against.
As an aside:
Personally, I don't love when people use a platform like this, where lots of newcomers to our industry frequently look to for answers, voice an ~opinion~ that borders on condescension about something that they don't seem to fully understand.
It's ok to have an opinion, or prefer different tools, but if you don't like something or don't understand the value, you don't need to pull a Tucker Carlson (???) response to it.
Yes, I've worked places where we do this. But I've also worked places where we use Bootstrap, and they're both bad ideas as far as I'm concerned. I don't think we should use modifier classes.
Who said anything about Bootstrap? I'm also very curious what you're doing instead of using modifier classes to style variants. Are you just writing inline styles? Duplicating other class styles to create a new class just to avoid this?
I guess everything else I pointed out you don't feel like addressing? It's pretty clear to me that you aren't interested in wavering from your original "opinion" here.
Best of luck navigating this industry with that mentality.
I did, me. What I'm saying is that I've worked places where we've used Bootstrap, which uses modifier classes and does basically all the things people criticise about Tailwind. Just because I've worked somewhere that does it a particular way, and where I go along with it because it's my job, doesn't mean I approve of something. So yes, I have and still do use modifier classes, and no I don't think they're the right approach.
Writing semantic HTML. Where there's an absolute client requirement to do something non-semantic, then adding classes beyond the semantic, but pushing back against it.
Everything else you pointed out was something I (mostly) agreed with.
I'm not sure why tailwind is so divisive, I've not met anyone who thinks it's "okay", we either love it or hate it.
Personally I love it and here's why:
I've always hated external css files, or one monolithic global css file. Looking at some html and then spending 5 minutes trying to work out what ut looks like was always frustrating. Sometimes SoC isn't a such a good thing. You mention Vue, but Vue's SFCs were actually made to increase the coupling of your html/css/js!
You can't do media queries with inline styles.
Tailwind let's you abstract your units. This is one of the biggest pros for me. I can use a class like p-3 in multiple places, but if I decide I want bigger spacing, I can just update my tailwind config and all of my p-3 elements will update. You mention colors, but these are just tailwind's defaults. In reality you would configure tailwind to have primary and secondary colors, e.g. bg-primary-light. Then when your cpany decides to rebrand, it's super easy.
If you don't like how you end up with super long class names, there are simple solutions. I use a simple concat library to split my classes into multiple lines. I group my classes either by breakpoint or by area (like all flex related classes) and it's really readable.
On the same topic, if you're using something like react tailwind should actually encourage you to make more components. All of my tailwind classes are neatly tucked away in low level components. My application level components are incredibly terse and have no styles or classes on them.
But the biggest win for someone like me, is I can just "get on". I don't have to worry about coming up with BEM names, or where to locate my styles, or how to keep spacing consistent, or compiling sass. I can work on the stuff I love (functionality) with the confidence that it will look good and consistent.
That said, I get a lot of the reasons people don't "get" it. But if you were to join my team I'm afraid you'd just have to get over it! 🤣
I get it.
I honestly think that Tailwind might be a good fit for you and your projects at this time.
I actually think a better fit might be a more opinionated framework, such as purecss.io/ - but forget that for right now.
If you are designing websites as a secondary consideration, if you're not comfortable with CSS, if you just want to "get on" with your structure and behavior, then maybe I can see it. In this type of scenario, I could see how Tailwind might be used as a rapid prototyping tool to try out different designs, but that once a design is settled upon, Tailwind code is rapidly stripped out and replaced with a more scalable, maintainable solution.
If I were to join your team, yes, I'd get over it. I'm a professional - if my team lead were to go with Tailwind, I would explain my concerns, state that I believe it to be a large mistake, but at the end, follow the team lead. That's what you do. Make your case to the guy who makes the call, then follow the call.
But if I was team lead, and one of my experienced senior engineers were telling me that a framework I was considering was completely worthless and would generate tons of technical debt for no appreciable value, I'd at least pause and think about what he was saying before plowing ahead.
I started writing CSS for money in the year 2000. I am very familiar with CSS and all the different ways you can use it, LESS/SASS/BEM/SMACSS, etc.
Tailwind provides a lot of value and solves tons of problems for me. I've been using it for about a year on a large Vue project. It's been really great.
You can always supplement it with hand-written CSS. Sure it introduces some problems (like anything else), but the benefits far outweigh the cons.
I love tailwind. I love the absolute tiny css file that goes along with it (when purged). I love that my boss will not mess up the rest of the site by editing a css style for his pet project page. I love not having to invent names for css classes. I love extracting tailwind classes to re-usable components.
You have bigger problems than I can deal with here.
I suppose in some cases, tailwind would be an improvement if your boss can go in and edit your company's css file for a project page. But then we're not exactly talking about best practices, are we?
You can replace the word "boss" with "others".
Still doesn't change the problem.
It seems like the problem is one of scoping, and tooling though, now that you mention it.
First, it seems like you're editing a global .css file. Now, whether it's SCSS, CSS Modules w/ a bundler (Webpack? Rollup?) it's... rare these days for there to be one universal CSS file precisely because web development has gotten much more complex. The output might be a single CSS file - that's the point of a bundler after all - but the input shouldn't be.
One of the key things you can do with CSS (and it's even easier in SCSS) is to introduce scoping.
So, let's say that you have a pet page.
All you have to do is, in the root of that pet page (whether it's
or or whatever...) is add )You can then write styles that apply only to that pet page.
It's even easier in SCSS
This is the power of CSS Combinators, one of the things you lose with Tailwind.
Sorry, I guess I should have been clearer. I understand scoping, components and most modern css best practices. I used to do agonize over all that with sass/less/postcss - code splitting across files where it made sense, components, namespace scoping, BEM and other methodology experiments. All with some form of build using npm/gulp/grunt/bundlers/etc. Now we just tailwind and don't really need the other stuff. It works great for our team.
It's sort of ironic that the site this is posted on took inspiration from Tailwind in our own approach to CSS!
It's entirely possible that was actually me. I produce a potent musk.
I respectfully disagree! Personally, Tailwind solves a lot of problems for me. For one thing: CSS is magic and I refuse to listen to anyone that thinks they can explain it to me because I might get hexed in the process. A tool like Tailwind keeps me safe from the dark arts (this is why I always burn sage when talking to @pp).
More generally, I think critiquing Open Source tools for their issues is fair (especially if those critiques are attached to a PR!), but we also have to be mindful of the limits of our own perspective.
As a CSS or front-end expert with a team of engineers to back you up, Tailwind might not be the best choice, but that doesn't describe all of us. When I build a landing page for a charity organization (I'm not billing, I can't handcraft all the CSS for this thing), I'm definitely using Tailwind or something quite similar. Tailwind is a great solution for those solo indie hackers out there; it improves productivity and abstracts a source of complexity that not everyone has the time or aptitude to dive into!
Another way to phrase it: more or less these points would also apply to Rails or Laravel, but those frameworks solve a lot of problems.
I'm not sure anyone wants to replace CSS with Tailwind! That sure is some all-or-nothing thinking (I learned about that in therapy, hell yeah)!
You use the example of Assembly and C, but I think this is more like C and Ruby; I'm not a C developer, as much as I try to learn about it, but I can build some pretty cool stuff really quickly with Ruby. Of course, the stuff I build in Ruby is going to have some weaknesses for having used that abstraction, but if I didn't use Ruby I probably wouldn't ever get the code to compile anyway.
However, I can go ask some friends who know C or Rust or something similar to extend my Ruby code when it really needs that performance boost!
Maybe one day, I'll have to re-write my Ruby, but that's a problem for millionaire Jacob. He has more users and paying customers than his little Ruby app can handle.
Tailwind doesn't stop you from cracking out that hellish CSS abomination whenever you want! When my Tailwind implementation makes something difficult I'll hire a warlock to summon whatever void-being we need to fix my layout.
While we disagree, I'm glad we can have a discussion about it. Maybe you're right; if people find this article searching for more context around Tailwind, they will find a lot of opinions (and the strong stench that comes with a multitude of opinions).
However, for now, I think the enlightened 21st-century philosopher-king Jason Mraz penned it more directly than I can every hope to: "You do you and I'll do me."
Oh boy did you get some reactions to the post.. I totally haven't read all the comments so I might just rephrase some other comments in my own words and maybe add my own two cents while I'm at it.
First of, don't use apply. Don't. I'd deny any pull request using
@apply
. Horrible feature. Yuck.From skimming through the comments I didn't see the point of standardization though. You can ignore all extra work of making sure all your devs in your team use the same kind of class-namings. To BEM or not to BEM, all these decisions go out the window, reducing extra time needed in planning that is better used to actually get to work.
The example you have in your section "It's hard to read":
How are they any different if you put the code into a component? All tailwind really helps me with is to not get fuzzy and build my own broken diy-css-framework over the time. It stops me from doing stupid things like getting into the habit of writing bootstrap-esque classes á la
.card .card-title .card-body
and so on. Instead of writing several classes myself now I write more components and think about making my project more modular, increasing reusability while maintaining a single source of truth. Now you might say "but I can have a source of truth too in my .css file". Sure. But why would I go through all the hassle and time to slowly write all the css classes myself that are literally right there from the get-go?Convenience and maybe a good portion of laziness? Yup, absolutely.
I'd probably just don't use it if there is some crazy specific task from a client where you can see right away that you're going to rewrite 90% of the tailwind config anyways. Overall it's just crazy fast to get websites done with tailwind while staying consistent in your markup, especially in prototyping stages.
So all that's left is probably to look at it by a case by case scenario and decide what tool to pick for the job. As always.
I agree. Not that I don't think you shouldn't bundle related CSS properties together into reusable classes, but that the whole point of Tailwind is to avoid that type of composition altogether. I may not agree with Tailwind's philosophy, but I don't mind pointing out as part of it's criticism when it's being philosophically inconsistant.
Weirdly, "making sure all devs use the same class namings" is not really a concern that I have. I mean sure, it's a good thing to aim for, but it's only really a "must have" if you're using an unscoped global .css file for styling. Most of the work I've done has been in Vue or React; which means I'm either using Vue's
<style lang="scss" scoped>
, CSS Modules: "import 'myComponentStyle.scss", or CSS-in-JS: "const myComponentCSS = css\
" (which probably won't show up right because of markdown not interpreting backticksI suppose if your team is operating on one big global .css file (instead of using CSS modules or CSS-in-JS) you have to make sure it is using the same naming conventions.
But for the most part, it's more important to me that class names are unique and descriptive of content rather than follow a certain format, so that they can be easily searched for in the source code of the finished HTML (for debugging) or understood by the rest of the developers on the team.
One of the patterns I'm seeing is that people who really seem to like Tailwind point out how much more useful it is than CSS, and don't seem to have a whole lot of experience or willingness to try CSS-in-JS or pre-processor (SCSS) solutions.
I have no problem with utility classes, used sparingly. Ideally I'd prefer SCSS mixins or resuable CSS-in-JS code compared to them, but what's wrong with that?
This is a compelling argument for a framework like Bootstrap, but not one like Tailwind.
One of the problems I have with tailwind is how many of it's classes - something like 99% -- though I haven't done the actual math, I'm pretty sure that's the right ballpark, give or take 1% -- are just one or two lines of CSS.
"p-8" is just "padding: 1.5rem;". "rounded-full" is just "border-radius: 9999px".
I did go through and identify about nine tailwind classes (out of how many hundreds?) that DO actually provide some convenience and am rewriting them in my own utility class library.
By all means, prototype away, but I wouldn't recommend it if you're dealing with end products meant to be delivered to the product owner.
I honestly don't use it as much as my text might suggest but I can see the appeal and tried to find some points to make you maybe see them from a different point of view :D
This caught my eye because I honestly never worked in a situation where I hand over code to another owner and I mostly work on internal products. The rare SaaS things I worked on were in good ol' SCSS though.
What would be interesting to see is the type of projects people work on using Tailwind. Is it a lot more agency-type of projects, internal sites (as in admin tools) or SaaS?
Immense thank you, @brianboyko !
So glad articles like these start to be written, providing simple and precise explanation of what is wrong with Tailwind.
My personal a-ha moment came after inheriting a project with it. It was supposed to be a no-brainer, after all you don't have to learn someone else's home-made CSS framework with Tailwind (they say)!
Yeah,
lg:pl-12 lg:pr-6 -mt-1 lg:mt-0
, give me home-made CSS frameworks any time instead of this mess.Some comments may only be visible to logged-in visitors. Sign in to view all comments.