124 comments

[ 4.4 ms ] story [ 161 ms ] thread
Author here: This is probably the most important content piece I've ever written. Curious to hear your thoughts. Thank you!
I share your sentiment and position on Tailwind. My first introduction to programming was in 2003 as a 10 year old with HTML CSS and PHP. I think we will see Tailwind, like other frontend tech, fade into the sea of choices.

If I had to summarize this I'd say don't fall victim to hype cycle and marketing. Make technical choices based on the problems.

I've worked on teams where CSS was a mysterious language with many gotchas. So I can see how Tailwind can help with this.

I can also see how Tailwind can help teams that just want to move forward by copy/pasting ready-made components without ever looking "under the hood".

However, I wrote this article for the love of CSS, web standards, and the web in general. I think Tailwind's dishonest marketing tactics aren't doing good for the development community. Especially for young developers who are suddenly told that things like separation of concerns and good naming practises are a bad thing.

In your article you highlighted that semantic markup can be much cleaner using a class like “primary”.

But in that instance, all of those css rules will still exist, but they’re stored in a separate css file and given a class name.

Having the reusability at the css class level means that someone else might break your component by updating the css.

With Tailwind, the reusability is at the component level. All the styles are self contained. No one can break your component accidentally.

You really need to cut out these accusations of dishonesty - it’s a bad look.

He’s not saying “separation of concerns and good naming practices” are bad things, he’s saying that separation of concerns between HTML and CSS is mostly a false separation (it’s the same concern - making the UI look right) and that good naming practices are hard - best just do that in one place, the component that address the concern with both style and structure.

I've been doing CSS for 20 years, Tailwind indeed lives up to the hype. Just the fact that amount of CSS code you need to send down the wire stops scaling linearly with each new component is a big benefit in its own right.
heyo- i made https://www.cutestrap.com/ and even though i haven't been using it much anymore, i share it to say I've thought a lot about CSS and I agree with your sentiments.

I think a lot of the complexity in CSS is accepted because of complexity in other parts of the stack. I've dug pretty deep into real-world use cases of many approaches to front end and ultimately, tailwind solves the #1 issue people encounter daily.

Fear Of Breaking Something Else Entirely

by never leaving the html tag for a secondary resource, confidence today is gained at the cost of future compound complexity.

as someone that specializes in design systems, tailwind breaks my heart because it it fairly reductive in expression. Truly white-labeling a product is not as simple as changing variables, it is more like a zen garden of css.

I don’t like Tailwind. But you probably have it the wrong way. You claim malicious intent such as vendor lock-in. Your article sounds as if all this was planned as a cash grab from the beginning. The more likely development path of Tailwind was:

1. Create a utility CSS system, use it, feels nice, publish an article around it why it might make sense and why it feels nice (which is probably a hindsight explanation)

2. People start using it, way more than ever expected.

3. Iterate on the product

That’s it. I’m pretty sure nothing about this was planned, the vendor-lockin wasn’t planned and just happened.

While your arguments about inconsistencies about semantic components in Catalyst vs semantic class names are right, it is highly likely your assumptions are missing a critical piece, because there are clearly many happy users.

I had a question for the author. Have you used Tailwind to build a project before? Or what has been your personal experience so far with the framework?
Advice from a professional arguer:

You seem to deliberately not present tailwinds strongest arguments. For example, you claim their is no next step to abstraction, but literally in the paragraphs before the ones you quoted they say to build reusable html+css components instead of trying to make css only abstractions. Which very clearly responds to your argument and complaint.

There are other places in your writing like this.

It's fine to think and claim they are wrong. But it would be a lot more persuasive if you take them at their best and knock them down, instead of being selective.

Overall take their absolute best case scenarios and reasons and show why you are still right.

Thank you for the advice! You are right. I'll do that in my next article about Tailwind.
Meh, tailwind is the most maintainable and easy to understand way of doing style I've experienced (from solo to massive teams). Been doing this for around 13 years, so plenty of time to try a lot of flavors of styling.

I'm so glad I no longer have to wonder what primary means, let alone the other weird names people come up with for classes.

And if you don't want to see the classes, just write it out into a reusable component called PrimaryButton, if that floats your boat.

> I no longer have to wonder what primary means

The point is exactly that you don't need to worry about styling in the HTML. You just structure the HTML. If you want to know what primary means that you open the css file and search .primary and see all the css properties right there. It was never rocket science.

The HTML was designed to be the structure of the site. So you can designate items as buttons, primary buttons, secondary buttons, success buttons, and so on. Then in the Style document (CSS) you would design what you want a button to look like, what a primary button would look like, how a secondary button should look like and so forth. Then together you have a website. If you want to change the primary color of the company from blue to purple than you change it in one place in the CSS and now the website is updated. If you want all the buttons to have a new hover state then you add it in one place in the css and the HTML doesn't need to know or care about it, but it updates everywhere. It was a simple system. Now we are designing inline in HTML.

Yes you could abstract it with frontend javascript components like you suggest. But thats like building a boat to cross a puddle. Not to mention it just brings you back to the original semantic styling context, but using multiple unneccessary layers of abstraction over what could be simple CSS. You suggest using tailwind to put into a react component, just to style a button with a semantic name. When you could use native css to do exactly the same thing with no dependencies or integration workflow required.

I think the crux of the issue is, the separation of HTML from css came from an era when we were styling documents, nowadays we're increasingly styling webapps.

For me personally, I've rarely had a design to implement that didn't require a bunch of scaffolding html to conform to the design, css alone was never enough. But that means for all but the most trivial of changes you will need to touch the html and the css.

So the question becomes, is it worth maintaining that separation?

My experience has been that most sites get built, get signed off on, run for some number of years before the site get redesigned and rebuilt, we're rarely making changes to a site that are just simple like the colour of something, all that was decided during development, most of it was signed off on during the design phase even.

So I think that's the angle Tailwind fans take, if the point of separating css from html is to allow for easy changes to the design, but those changes are either never required or can't be achieved via css alone, what's the point? Better to go inline because the development process is quicker and easier.

I don't think I'm going to change your mind since you're fairly convinced of the ideological / purity standpoints. But folks have tried that stuff for years. It didn't scale, or end up maintainable. So they invented systems to try and help, like BEM or less.

I don't understand why you think this is like "building a boat to cross a puddle". Folks have struggled with this since the dawn of the web. It's hard.

Most of us aren't crossing puddles, we're trying to cross oceans with gigantic diverse teams.

While I agree with the principle of "clean markup", whenever I open up the DOM of websites I did not create (and I do so with regular frequency), I generally do not see what I'd consider clean. The author is correct but it's not that frequent of a practice; I wish it were so. Care for our craft is an uncommon thing.
Tailwind is write only, you’re not supposed to read it or maintain in. Want to change some property? Delete the entire class=“” attribute and start anew. Take that primary button example from the article - no one is claiming that it’s readable, especially when there are hundreds of primary buttons throughout the app, each with a slight variation.

This works great for marketing or landing pages, or other use cases where it’s not you who maintains it after deployment; the next sucker will want to rewrite it all anyway.

How’s that different than just writing plain CSS?
Tailwind limits the scope of your rewrite problem to the element you want to change. No fears about side effects on other parts of the page.
so if I write into "style", I have all advantages of tailwind?
No. There is no inline style for @media, :hover etc.
Also inline styles are not GPU accelerated like CSS classes are, so there’s a performance difference too
Wouldn’t using @scope or CSS modules resolve that problem?
(comment deleted)
This is just demonstrably not true.
Agreed. I read and maintain tailwind every day. Rewriting it regularly would be highly unproductive.
I modify it all the time so I'm not sure what you're on about.

I have a feeling people hate Tailwind for making CSS accessible to the masses.

Totally agree, it's mostly gatekeeping at this point.
> especially when there are hundreds of primary buttons throughout the app, each with a slight variation.

If you're repeating monstrous groups of styles like the one in the blog post, but with a slight variation for each, then you're tremendously far out in the weeds and almost definitely shouldn't be using tailwind

Tailwind really only works for a component-based framework (like React, Svelte, Solid, etc.), where you define a component representing a button, and give it props to map to the 'minor variation' you want.

That way if you need to support e.g. a new device class, you just add the relevant class names there and don't have to revisit your entire codebase.

This isn't apparent at all when inspecting the source of a site using tailwind, because as far as the rendered markup is concerned, these are getting repeated (and the verbosity of the output is absolutely a fair criticism)

But if you're writing it, and organizing your components in a sensible way, then the problem you mention doesn't exist.

I don't see the value in Tailwind. It gives you convenience but adds complexity, one more thing you have to learn and makes it easier to shoot yourself in the foot. Also, I like the no-bundling approach to application development with Web Components and Tailwind goes against that.

I prefer using plain CSS. I like semantic CSS and similar ideas of associating certain component class names with certain styles.

Like many I was initially skeptical of Tailwind because I thought it made the code look “ugly”. But then I started using and almost immediately noticed the productivity boost. Two things that I noticed while using it was 1) I never knew how much I disliked naming things before I didn’t need to do that anymore and 2) how big of a mental burden it was to keep the mapping between the id/classes and the CSS, not mentioning the whole cascading part where you could never actually be sure just from glancing at the code what will be the resulting css. That second point is probably the most understated feature of Tailwind - you never have to wonder about the styling of an element again, because everything is right there in the classnames. The added benefit is that it basically forces you to write your UI code using components to prevent repetition - it encourages the best practice (note that this is where I think the OP went wrong when they asked “How to clean things up?” - the “@apply” keyword exists but it’s not meant to be overused as a replacement to proper code refactoring).

It’s not without its flaws - you have to learn a new syntax (which is in most cases very intoitive, e.g. “align-items: center” becomes “items-center”, “margin: 0” becomes “m-0” etc.) and it has its gotchas (for example it generates the css at build time by scanning your code and extracting classes you use to prevent generating unused css, which makes it impossible to construct a classname dynamically e.g. `gap-${value}`) but I take these issues over having to write CSS in any other way.

One point where I 100% do agree with the author though - you should first learn CSS before learning Tailwind. Not only because Tailwind could go away some day, but because this will give you a deeper understanding how and why stuff works.

Naming things is notoriously hard, but it's a skill you need to master to make reusable things. You name things that repeat. Think of function names in JavaScript, class names in object-oriented languages, or component names in Figma.
Ideally we shouldn’t have to name things that are never reused. Often class names are only ever used once. There isn’t much benefit in naming the class.
Yeah, this is exactly my point. I still need to name my reusable components, but I don’t need to name every single html element I want to style.
You only need to name the top-level element. Others can be styled with CSS selectors. Nesting is supported with Lightning CSS today.
You can still name things in Tailwind + React (create a new component or variant), what changes is that if I need to add a 10px margin on the right I don't have to create a new component (like when using styled components), I simply override `className` and move on.
Tailwind lets you stick to just naming your components, don't bother trying to give names to all the re-usable sets of styles
as a designer, ability of naming thing is extremely crucial regardless of how hard it is. As you mentioned figma, I agree that not just the components, but also frames should be named properly.
> 1) I never knew how much I disliked naming things before I didn’t need to do that anymore

I never understood this argument about naming. You need to name the component anyway just to be able to reference it somewhere? You need to give the folder or the file where this component is defined a unique name anyway? Internal nodes technically don't need to be named when using Tailwind, but if for example I'm writing a Slider component, do I not want to name the "handle" thing that can be dragged anyway? How was finding the "handle" word a non-insignificant hurdle? And if I need to talk about this element with a colleague what would you call it anyway?

For testing you usually need a name or "id" or, if using Playwright, a "data-testid" attribute :)
Unlike many, I was never skeptical of Tailwind. A few years ago, I'd chase whatever the "cool" and "trendy" thing in the market. So I started using tailwind in 2021. Initially it felt very productive especially for personal projects. Then over time, as utilities increased, so did my cognitive overhead.

Also I'll never be able to understand to point of having extremely atomic utils like `bg-color-green-100`, `pb-40` and such. We have custom properties for that and for a large design system, maintaining variables in a css file is more ergonomic than editing a tailwind config.

> “optimizing for reusable CSS was going to be the right choice”

That is what CSS is. In the melee of “CSS in everything but CSS,” people might have forgotten what CSS is for. CSS’s classes are what you use everywhere again and again.

CSS Utility classes have been there for a long time and were made easy and famous by the likes of Bourbon[1]. They even have pre-defined styles — Neat[2].

Even though Tailwind can and should have been used more as what it was destined to do — utility classes. In a larger team, it is easier to stick to the TailwindCSS as such and have specialty/custom classes based on its utility for your project.

I was checking it out during its early release and introduced Tailwind to a team in 2018 who were stuck in Bootstrap. I wanted to stop hearing, “This is not part of Bootstrap.” The good thing I realized then was that if I followed a custom class with utility based on Tailwind, the result could continue to be the same with someone doing a “button button-blue button-large text-xl text-white.”

Btw, this too shall pass. I suggest learning CSS from first principles — everything that comes later will be another syntactic sugar. Remember, we started our projects with our grid layout that we stole from A List Apart’s faux-column layout, then came blueprint[3], bootstrap[4], foundation[5], bourbon, and now Tailwind, and every other one in between.

There was even a paid service (or a few, I think) that just churns out custom CSS grid layout code based on your drag-n-drop sliders.

1. https://www.bourbon.io

2. https://neat.bourbon.io

3. https://github.com/joshuaclayton/blueprint-css

4. https://getbootstrap.com

5. https://get.foundation

I find it fascinating how Tailwind divides opinion.
(comment deleted)
[flagged]
Isn't the article linked to here also dishonest?

As in, the author compares a large box of complex styles to a single class attribute, without showing that to have the same function, those styles will also be written out in the CSS file. The contents of the CSS file should also be shown.

I have no idea how tailwind has gotten so popular.

Tailwind is a way to justify writing inline css in our HTML. This was once considered the cardinal sin of a frontend developer. You avoided inline-css at all costs.

But now Tailwind just makes a class for text-color-blue which simply abstracts the `text-color: blue;` css property away into its own class. But don't fool yourself, you are just writing inline css by a different name.

There is no way to explain how it is more maintainable to write `class="rounded button hover:bg-color-black-700 bg-color-black-400 active:bg-color-black-500 focus:bg-color-black-500 rounded-sm text-sm text-color-gray-300"` instead of just `class="btn btn-primary"` using the "old-school" semantic approach. I can list numerous advantages of the semantic approach here. The only advantages of the tailwind approach is that you can style as you go and you can see the resulting style in the HTML page.

Tailwind fanboys will say "it's easier to style in the html". But that's not true. Tailwind to be done right needs a fairly complex set of tooling to enable the hot-reloading, minification, and parsing into your pipeline. None of this is necessary with simple CSS. Semantic CSS "just works" with no tooling whatsoever.

Then again, I'm old enough to have contributed to the famous CSS Zen Garden back in the day. So maybe I am just an old curmudgeon. But like all things, I expect that Tailwind's popularity will wane. Remember that before Tailwind, everything was Bootstrap. The point is that whatever tool lets developers be lazy and avoid learning fundamentals will be the tool that prevails.

Hey, I'm old enough to have learned CSS from CSS Zen Garden!

The one thing I appreciate from Tailwind CSS and I'd like to see added to CSS itself is that I don't have to write media queries everywhere.

Nothing destroys the readability of my CSS more than adding multiple media queries to adjust styling for different screen sizes. I wrote a component that used CSS Grid and needed to vary the layout as it resized, and over half of the CSS is now media query definitions.

So having a built-in way to streamline defining media queries, and a built-in way to select in line styling from breakpoints a la `py-2 md:py-6` are my 2 asks for CSS after using Tailwind.

(I would prefer to do away with many breakpoints and have a properly fluid design, but IME it's a lot more work and designers can't get their heads around it, let alone clients. And where images are involved it's easier to have a smaller range of possible sizes for the srcset)

I have a hard time articulating exactly why, but I believe tailwind is popular because it allows developers who are not designers to ignore design/css best practices and just write inline styles again like they used to, before everyone starting telling them that this was bad practice.

I had a rough road learning css, and have been in a few situations where my css/design skills were insufficient to the tasks I was expected to solve. Inline styling let me work more intuitively, and in large part skip learning a whole new paradigm/architecture "just to style some components".

As I eventually grew and had time to work on styling/design in a less stressful environment, I gradually got better at it. Not by practice so much as by learning a new way to frame the problems of styling differently from the problems of "pure" programming.

Had tailwind been around back then I'm sure I'd be a big proponent. It's a product that proposes to solve the cognitive dissonance for a programmer with lots of experience coding, but little experience designing being asked to structure and reason about their work in an uncomfortably different way.

I think tailwind solves a real problem, just not the one we're being told it does.

Tailwind is popular because it works really well for a lot of people. It's not about marketing or people being tricked by marketing.

Sure - it's very different to the old way, and takes some getting used to, but a lot of people enjoy using it.

But also people can still build sites the old way if they wish - that's ok too.

I think the divide here is whether or not your stack includes the concept of components. I think it's perfectly maintainable to write `class="rounded button hover:bg-color-black-700 bg-color-black-400 active:bg-color-black-500 focus:bg-color-black-500 rounded-sm text-sm text-color-gray-300"` because I only write it once, in my Button component. Then everywhere else, I just write `<Button>Inner Text</Button>`.

The key difference is when I want to change how my site's Buttons look or behave, I only have to look in the `button.ex` file, instead of tracking down the relevant button classes in a mess of CSS files.

Tailwind is not a good solution if you're writing something for CSS Zen Garden or a my_blog.html type website. Of course the repetition will render it an unreadable mess. But it is a wonderful solution when you have a framework generating the HTML for you.

> because I only write it once, in my Button component.

Unless of course you have several similar buttons with only minor differences between them. Maybe copy-paste?

And then you have to slightly tweak your buttons... Or add conditional effects or dark mode and the complexity explodes, just on a simple button.

The same is true of CSS classes, though - there you have the same choice between reuse and duplication. Do you have .button-primary, .button-secondary, etc, or do you use modifiers instead? The combinatorial complexity is inherent in the design of these sorts of components, you won't get away from it just by using a different tool to write your styles.
But CSS handles it much better, which is the point.

You separate it into a .button class, and then further specialize it by adding more classes (like ".button .button-primary").

With Tailwind everything is duplicated, but with CSS you can move out the common parts.

Nothing holding you back doing exactly the same with Tailwind.
Of course, the solution to Tailwind's problem is to refactor it with CSS classes...
And to do this with a component framework and tailwind you give your <Button> component some props that customize it.

With CSS you also have to worry about the cascade way too much, so while you are doing much of the same work with Tailwind, you get the nice benefit of inlining the styles which will actually apply to your component. Yes, you still have to think about organization and how to make different styles apply to variants of the main class, but it's so much easier to not also have to worry about how cascading rules might apply and constantly check different rules for specificity and overrides

I am curious what you - and with you more proponents of Tailwind in this thread - mean with “component” and “component framework”? I’m also Zen-garden age and with my very limited React experience this does sound like you are meaning a component framework like React, correct?

If so; what would be the motivation for using JavaScript to style a button? Call me old - my coworkers do -, but that feels counterintuitive.

Please note that I haven’t really formed an opinion on Tailwind yet. This is an honest question.

Component in this sense is typically just "a composable unit of UI". That often corresponds well with JS frameworks that want to be able to attach state and behaviour to those units, but you can have components without needing Javascript.

For example, partials in classic server-side rendering libraries can be thought of as components, although they tend not be used at such small scales. In CSS patterns like BEM, the B ("Block") is in many ways a component with different sub-elements and states. This is partly why Tailwind provides the @apply directive - it allows you to create "CSS only" components even if you don't have other ways of creating components. That said, other forms of components are often easier to use, and if you already are using them, Tailwind recommends using them for style reuse rather than @apply.

By "component framework" I was honestly thinking of React, Svelte, Vue, Solid, Qwik, and Angular, though I don't actually have experiences withh all of those.

I also don't have experience with native webcomponents or HTMX but I believe it would apply to those as well.

I do have experience with HTML templating such as Jinja and ERB and it would absolutely apply to those as well (though I don't think those would count as component frameworks, and certainly wasn't what I had in mind)

Basically some system which allows you to write some "unit" of markup such as

    <li class="search-item-result">
      <div>
        <img src={item.icon} />
        <a href={item.url}>{elided(item.url)}</a>
      </div>
      <div>
        {item.previewText}
      </div>
    </li>

Which represents some logical unit which should be displayed in a consistent manner (with allowed exceptions, you can style the first and last of the result list differently using CSS selectors or tailwind classes)

With Tailwind, you just drop in the class names you want to spruce those up, and your component framework or templating library will give you a way to render a series of them with different properties. You have to repeat neither the markup nor the class names for multiple items

This is possible with CSS of course: Just add rules that apply to `li.search-item-result`, `li.search-item-result>div:first-child`, `li.search-item-result>div:last-child` but this creates a number of problems for maintainability. Say you want to wrap the first div in another element, or change the tag type, or add a spacer element before it. Now you have to repoint your CSS rules.

Additionally you have to think about the cascade, and how your CSS rules might apply to elements you didn't intend them too.

There are numerous systems for resolving these issues (BEM mentioned by the sibling commenter being one of them), but you end up writing a bunch of class names (and IDs for elements you only intend to render once on the page) which you have to name and reference. Then you have to probe devtools to determine which rules apply to which elements.

I can empathize with devs who have their own systems dialed in and don't think Tailwind brings them much value here, but if you don't have that system dialed in or just don't like thinking about names and specificity, it's a massive boon. Also, you end up writing a lot less code altogether since you don't have to write selectors or custom class names for everything you want to target precisely.

It is completely possible to do that with Tailwind and components, I do it all the time. I'll create a Button component that takes parameters like `mode=primary`. The Button component contains a `button` element with some common classes that apply to all buttons (padding, borders, shadows, etc). Then depending on the parameters, I'll append other specialised classes (usually colours).

The only thing you have in CSS that is harder when working with Tailwind is overrides - e.g. "this component is normally blue, but it's green if this class/parameter/attribute is set". That said, even when I'm using CSS, I tend to find overriding, except in the simplest of cases, usually causes problems, because now I need to make assumptions about the order of declarations that are difficult to make in a codebase maintained by multiple people over several years. For that reason, in the button case, I would typically make it so that the different modifiers (.button-primary etc) are mandatory when using the .button class.

I'm not a CSS guru, and as a result I've had to do things in a very very simple way. Currently the simplest solution for me, involving no additional server side frameworks is to just "compose" the static html on the server or at build time. So a "base" class of re-usable styling could easily be a static string. I wouldn't start by thinking about inheritance, I'd only consider it if and when I actually hit that problem. It's often just a single edge case.

So my code is generated server side, using something Jam-stacky like Hugo or Cloudflare pages, (with a cloudflare worker that dynamically replaces or modifies a button ...in that very one specific edge case.)

Your existing example needs to be made more complex though because I'd just use standard CSS for that, for example button.submit { color:blue) button.submit.critical { color:red }

i.e. all my buttons are blue, except that one special case where I have a "critical button" and we "override" the color to "red". I simply flag it with the extra semantic attribute and style it accordingly.The button will clearly be red because the css for red has a higher specificity, but it also "reads" easily as such if the css is properly crafted. Of course it's possible to write Semantic CSS that no-one can follow; and the same goes for Tailwind.

The problem with CSS though, is unfortunately the most common approach is to add a new DIV, and slap on a CSS definition that ignores all inherited styling. So, unless you're going to re-write everything a big corporate ball of mud is made up of, Tailwind works better here, since it's PURPOSE is overriding and ignoring everything. He says ..ducking!

Is this really an argument for Tailwind though? isn't this just an observation that both approaches seem to converge when the systems get very large and complex? At which time the work and effort is the same, except ..in the case of Tailwind you're using a made up syntax, and when writing similar amounts of code using CSS you can do it using the CSS standard. The key difference is that you would have to hope that Tailwind outlives CSS as a standard in terms of tooling and language support and continuing improvements (evolution) over time.
Definitely, Tailwind is just one approach to solving this problem, there are plenty of others. That said, one of the big advantages of Tailwind over other approaches, at least in my experience, is that it is far less bound to typical JS workflows, which means if you want to write HTML with your own favourite templating language, you can just drop in the Tailwind CLI and still get a lot of mileage out of the system. Compare that to CSS-in-JS or CSS modules, which are both very specific to the Javascript ecosystem.

The other big option is just using a methodology like BEM and being consistent with that, which in fairness also isn't bound to Javascript and has the benefit that - like you say - you're writing CSS as opposed to a DSL that is mostly equivalent to CSS. For me, the problems there come from the lack of tooling for writing really maintainable CSS - stuff like scoping and detecting whether a class is still in use. But it's definitely still an alternative, especially with some of the new features like nested selected that are coming out now.

> I have no idea how tailwind has gotten so popular

- marketing - influencers - novice developers who blindly follow infulencers - great documentation

One thing I would like to see is a re-implementation of the button in your style of CSS to strengthen your position. I'm not siding with either approach, I just think it would make the article better.

Your, "don't make things cleaner"/"make clean things", images show part of the Tailwind classes used to make the black button on one side and the class "primary" on the other. Elsewhere in the article you define "primary" as only setting the text color to white.

I believe the specific example is a strawperson argument - you're asserting that semantic CSS (as you've defined in the article) is better than Tailwind's approach. However, you don't show an alternative.

---

On a personal note, I've never seen custom (S)CSS written in a maintainable way in my ~decade writing it professionally.

Encapsulating styles by colocating them with the markup (Button.jsx and button.css, styled-components, Tailwind, etc) is the most maintainable way.

I think Tailwind works really well because it eliminates collisions at the global level and makes it really easy to see what a piece of markup will look like without having to change contexts.

As a fan of Tailwind's approach, I like to hear the other side.

Hey. Thanks for the comment. I could add that, but wanted to focus on the title of the article: the technological origin, vendor lock-in, and their messaging scheme.

I have written a different article about Tailwind vs semantic CSS, which compares the amount of code needed to implement the same thing and how the resulting site is order of magnitude smaller with the semantic approach:

https://nuejs.org/blog/tailwind-vs-semantic-css/

I dismissed most of the content because you’re missing a full css re-implementation of your primary class. It would have at least as many lines as the Tailwind classes and have extra blocks for before, for example.

Either way, all these classes/css end up in a Button component for my site.

> at least as many lines as the Tailwind classes

Have you ever used vanilla CSS? Please try to replicate that black button! You'd be surprised how much less it requires. No &amp;>[data-slot=icon]]:-mx-0.5 or anything like that needed

I have for many years! And this block needs media queries and stuff for active and other states. That line you quote is .primary>[data-slot=icon] { margin-left: -0.5rem; margin-right: -0.5rem: }
And you still need 60+ lines to create a button like that?
Come on - your turn. Come back with your version of that Catalyst button that does everything that’s being done by those Tailwind styles.
I've written an entire article that about Tailwind's "don't clean things up" attitude:

https://nuejs.org/blog/tailwind-vs-semantic-css/

The bloat takes several forms

1. The amount of source HTML/CSS needed

2. The code complexity (class & DIV/SPAN "soup")

3. The weight of CSS & JS on the resulting website

Happy to write a comparison to Calypso, which is on a whole new level. Betting 10-20x bloat differences to semantic CSS.

Both this and the post you linked are great.

I do think it would be great to make a follow-up post on Calypso, even if only on the button. Looking forward to it.

I think it's important to add to the article. Like I said, the one bit of CSS you added is nowhere near the functionality of the Tailwind example you were comparing it to. Without an apples to apples comparison showing _how_ Tailwind should've done it, your words have less power.

---

I read your other article and it still feels superficial and missing details. You compare a demo of a commercially available (effectively "closed source") template against a page you built, but don't provide the source for the page you built. Apologies if it's in the article, but I couldn't find it. I did find the simple blog template on the Nue site, but it had considerably less functionality than the Tailwind demo.

It's still not an apples to apples comparison, because you're comparing a site built with Nextjs to whatever you built. Of course static HTML without JavaScript is going to load faster than an SSG site.

I think the ending quote in the second article is completely unrelated to Tailwind and I'm not sure why it was your conclusion.

> The fact is that Tailwind's popularity will eventually fade. CSS-in-JS is trending now, but standards are forever.

Tailwind isn't CSS-in-JS. You don't need a build step to write it. You need a build step for important features like tree shaking and dynamic properties, but it's not CSS-in-JS.

Including that hurts your position because it sounds like you don't understand what you're arguing against.

Lastly, why so much hate against Tailwind? Positioning your framework as "Nue and semantic CSS vs Tailwind" doesn't make me want to try your framework. Show me why your framework is good and let me bring my own solutions to it.

I think Tailwind appeals to « back of the front end » devs which is a role that didn’t exist until 2010 or so?

I think tailwind is valuable and as always middle way is the best approach but more difficult to structure and definitely not good for commercial purposes.

My favourite way is to put tw classes for spacing components and layout directly in the template while using bem-like naming for the component ‘s own appearance.

Having utilities like mb-2 that can override the default set in a component I think solves many headaches and is one of the strengths of having a full set of utilities.

However I also find a real strength is having a standard accross devs AND projects and so when tailwind advises to customise your project. Change the meaning of mb-4 or text-lg then I find it completely undermines the value of having a standard utility set.

Makes sense. It's likely more tempting to people without years of experience with vanilla CSS: its specificity rules, and how it cascades. I guess the people who value the separation of concerns pattern in frontend architectures and are super hesitant to apply inline styling.
I guess you're missing the whole point of an entire market segment adopting utility CSS like tailwind or uno (and windi before that) for reasons. It wasn't an arbitrary decision and I very much doubt it was mostly decided because tailwind guys are good at marketing.

Semantic CSS class names are a failure because the context in which they are named is often a moving target. In the real world, a good name for something at point A in the timeline is not likely to hold up at point B for the majority of developers out there. It also relies on a persons capability to extract meaning from an arbitrary name and hope that their understanding of what that means lines up with the original developer's intention of what it means. And then that can fall apart if you are working with teams with different cultural perspectives and language proficiencies.

Utility CSS also lets you tie into an underlying design system that enforces consistency. You know someone doesn't know what they are talking about when they make the claims that tailwind is just inline styling because it isn't. Inline CSS styles are absolute. Utility CSS classes are tied into a design system that can be mutated and changed.

Honestly, your whole Nue project is written about in such a way as to be mildly insulting. Like I would never even consider using it regardless of whatever supposed superiority it may or may not possess because you kind of come across as a pretty unlikeable know it all. I don't know if that's true or not, but that's the impression I get reading the stuff you've written. This article is sort of another example of it. You literally tell people who use tailwind CSS to "learn CSS" and that we are taking part of a "trend". It's got the real dinosaur vibe get off my damn lawn vibe going for it. That's saying a lot because I'm pretty sure I'm older than you and I've been doing this as long, if not longer.

I find it a little bit backwards that semantic CSS class names should be invented by the developers. If there's a working and well-organised design system in place, then there should be little need to come up with arbitrary names for things. At the very least the designer(s) should be consulted, because they have experience and insight into which elements have common styling with each other and how they are placed in context. In general, I think Tailwind's popularity has a lot to do with developers typically not being designers.
I agree with the overall sentiment of the article, but find it a bit harsh.

I honestly find the real debate to be inline styles vs stylesheets.

Tailwind is just fancy inline styles. Personally, every single time I've used stylesheets in a large production-grade project it's an absolute mess. Every project I start now I use inline styles. I'm pretty indifferent on Tailwind though, it's sometimes nice to get opinionated defaults.

It's not just inline styles. Idk why so many people comment on these threads with no experience using the framework.
(comment deleted)
(comment deleted)
I like Tailwind because it's easier to prototype & modularize design. The HTML can be copied from templates, other sites, & it more or less works. Carson Gross, creator of HTMX, wrote an article about Locality of Behavior, which I believe applies here.

https://htmx.org/essays/locality-of-behaviour/

I don't use HTMX as I would like a smaller JS bundle. I would also like an efficient CSS system of utility classes. I think Hypermedia libraries & CSS utility libraries can be improved. I'm also working on app systems to reuse code across domains...using a consistent domain language. A challenge is interoping my domain ontology with other domain ontologies. The class name interop is an area where Tailwind has found success. You can start with their prototypes or another developer's prototype with copy/paste. This is great for engineers/developers who are less inclined to spend enough time learning the art of perfecting design.

This can happen in semantic markup as well, however the bounded context of the example site is less likely to interop with your app...because the semantic markup is less bounded than utility classes.

Tailwind also has `purge` to remove unused CSS classes. Yes, `purge` is a build step & it requires the js/html content to evaluate. However, if you want to optimize the JS bundle size, you probably use a build step.

> At some point, hundreds of utilities later, the code you've written doesn't look pretty. You start wondering what comes next after the utility-first step. How to clean things up?

> Turns out there is no next step. Or it kind of exists, but it's called "premature abstraction". You can start extracting classes with @apply, but the documentation for reusing styles [link] describes it as a bad practice.

> But what should I use @apply for if not for cleaning up? The documentation does not say. It only tells me why it should not be used.

If you jump to the Tailwind section they link to here, the short section immediately before explains with an example you should be using HTML templates to reuse styles over @apply, which is really core to understanding Tailwind:

https://tailwindcss.com/docs/reusing-styles#compared-to-css-...

  Unless a component is a single HTML element, the information needed to define it can’t be captured in CSS alone. For anything even remotely complex, the HTML structure is just as important as the CSS.

  Even if you create classes for the different elements in a component like this, you still have to duplicate the HTML every time you want to use this component. Sure you can update the font-size for every instance in a single place, but what if you need to turn the title into a link?

  Components and template partials solve this problem much better than CSS-only abstractions because a component can encapsulate the HTML and the styles. Changing the font-size for every instance is just as easy as it is with CSS, but now you can turn all of the titles into links in a single place too.

  When you create components and template partials like this, there’s no reason to use anything other than utility classes because you already have a single source of truth for the styles.
> Components and template partials solve this problem much better than CSS-only abstractions because a component can encapsulate the HTML and the styles.

One of the often overlooked dividers between those who like Tailwind and those who don't is whether your Web framework of choice has a concept of components.

Template partials with their lack of properties and a defined signature (hi autocomplete!) don't cut it in my experience.

Oof.

First off, it feels weird for the developer of NueJS to be throwing shade at other tools for "marketing and misinformation", given the number of articles I've seen extolling the virtues of NueJS as the silver bullet to solve all your problems.

Secondly, I think it would be useful for the author to reflect a bit on what Tailwind is doing that people find so useful. Obviously not everyone loves it (personally, I prefer writing CSS to Tailwind, just because it's a much clearer and expressive language), but it's clearly very popular, and the reason for that popularity is typically unlikely to be a conspiracy involving easy onboarding and lock-in.

I think understanding Tailwind on its own terms, with its own merits, is really valuable for understanding the state of CSS development right now, what works well, and what needs to improve. The author here seems to see Tailwind purely through the lens of being able to write CSS decorations: in Tailwind I might write `m-4`, but in CSS, I'd write |margin: 32px`, these are equivalent, so why create a whole extra tool just to do what we could already do?

To me, though, this is deeply missing the point of Tailwind. Tailwind isn't just a different way of writing CSS declarations, it's a way of tying those declarations to components. This is nothing new - it's the same concept as BEM, or CSS modules, or CSS-in-JS, or scoped CSS, amongst others. Essentially, when writing large projects with extensive CSS, most people run into maintainability issues, and most people seem to develop similar solutions: they tie styles to an already existing, more maintainable unit of code, namely the component.

I can go into the details of where these maintainability issues come from, and why components are such a nice solution, but if you've worked with large CSS projects, you probably already know that stuff, and if you haven't, I think BEM is a good place to start for understanding how to organise CSS, and why using the concept of individual components with sub-elements and different states is very helpful.

Understanding Tailwind on its own terms, then, we start to get answers to a number of the questions that the author asks. For example, they compare a JSX-style declaration `<Button plain>` to an equivalent with classes `<button class="plain">` and ask whether the difference is just syntax. Well, let's analyse it from a component perspective: with the former, I know where the `Button` component is defined and can analyse it with my editor's code navigation tools, and I know that `plain` is scoped purely to that button. I also can't accidentally use `plain` from a button to annotate a checkbox - plain here explicitly refers to the button's "plain" style and it will not get applied elsewhere.

The same can also 100% be true in CSS - like I say, I'm not a huge Tailwind fan, and I personally prefer CSS solutions where they make sense. But it probably wouldn't look like the author has written it, it would probably look more like this: `<button class="button button__plain">`. That said, this still can have issues - the `button` class is completely global, and woe betide you if you forget it exists and try and define another `button` class somewhere else (or important one from somewhere else).

These questions of scope are hard ones to solve, which is why Tailwind exists alongside so many other, similar solutions. And I can fully understand why someone may not like the Tailwind solution specifically - its DSL for defining decorations is idiosyncratic, particularly if you're already familiar with CSS. But this article doesn't contrast Tailwind with those other tools, instead it compares having a solution for these issues with just not having a solution at all, and then tries to tell you that ignoring the problem is the better plan. I think that's a very disingenuous argument (possibly even an example of marketing and misinformation?) that makes ...

For me, Tailwind is great because it actually popularized utility classes. Sure, there were others that did it first, but the approach as a whole is widely accepted and seen as a productivity boost. I remember when this was thought of as a really bad idea when it's clearly not. Now I think I have written more CSS than a majority of the population[1], and for me, the most productive workflow is the following:

- Semantic class names for components like buttons, sidebars, etc.

- A wide range of utility classes to fill in the rest.

Utility classes (in my opinion) are absolutely the best way to handle different types of layouts, positioning, and spacing. Especially when it comes to overriding the component level ones from the semantic class names. This is not a novel approach by any means, I mean Bootstrap does it and it's still great.

[1] https://www.gethalfmoon.com

Makes sense. There are multiple reasons to group things and give them a semantic class name. And inline styling is a common practice to override parent styling. This is different from what Tailwind teaches us.
The tech industry is full of thought leaders trying to sell us their inventions.

Adam should have said that Tailwind is a good solution for him. Instead, he said that Tailwind is better than everything else and piggy-backed on famous people in tech to sell his idea. Which created a cult-like following.

Cults are bad for tech because it prevents out-of-the-box thinking.

I think that a huge part of Tailwind‘s success is not so much the underlying concept but a combination of several aspects: You get a kind of generic design system (margins, paddings, matching colors, etc.) that are restrictive enough to help make things look good and consistent but flexible enough to not result in a every-site-looks-the-same situation (at least not to the extent many other CSS frameworks did); Tailwind sits at a low level of abstraction above raw CSS that is just enough to make some CSS concepts a little bit easier to use (flex, grid, etc.); the documentation is quite good and getting started is relatively easy; …

Given these (and probably some more) aspects, Tailwind makes it easy to get some good results quickly which helps to convince users that their whole approach is the right way to go. Add very good marketing and early traction and you are where we are now.

So I agree with the article that Tailwind‘s conceptual approach is not the holy grail it‘s often said to be, but it maybe also is not even the reason for its success compared to many auxiliary things it did/does right.

Option 1: Find a visual error, open devtools, find the nearest most descriptive class or id (.primary? Oh no…), open IDE, global search in styles folder structure (most likely but not the only place to find CSS) for “.primary”. 40 files returned. After 15 minutes find a file that looks like it might be the right one. 10 minutes to understand the CSS (lots of advanced CSS in here…). Find the cause of the visual error, make the 1 line change. 30 minutes - job done.

Option 2: Find a visual error, open e.g. React Devtools, find the component name, open the IDE, find the component source file, find the HTML, change the utility style class, job done - 5 minutes.

These are not contrived examples. I had a one liner take 40 minutes to find in a codebase a couple of weeks ago. Not an exaggeration.

I really used to believe in separation of concerns but I don’t any more, because of all the time I’ve had to spend fighting with “option 1” codebases over the years. Pretty much any big codebase with normal CSS is going to be an “option 1” eventually unless it’s the work of a single good CSS developer (rare skill), or a team where someone who’s a good leader also cares about CSS enough to enforce a convention like BEM in perpetuity (even rarer skill).

Anyone building anything complex these days is using some component abstraction - whether it’s client side or not - to manage complexity. May as well make use of it for styles if it’s already there.

The reason that there’s a backlash against separation of concerns, the cascade, and (low) specificity is that each of these things is a complete disaster for maintainability in the long run.

One note in making an example of that Catalyst button. If you wrote out all the styles required to do the amount of work that button is doing it would also be extremely difficult to understand, and what’s more, it’d be in a completely different file to the single thing that gives it any meaning - the HTML.

This is exactly my experience as well. CSS in bigger codebases becomes unmanagable pretty quickly, and making small changes can become a big pain point. Tailwind doesn't completely solve this problem, but it absolutely improves it by reducing the distance between related concepts - and that's always a good thing.
I believe there are two problems Tailwind tries to solve: styling and layout.

For laying out items it might be useful but for styling it is not (why would you create a separate component for each HTML element, like a button!?).

And I even don't get the styling example in the article.

Why would you use a 'primary' class on a button while the button type makes it primary?

If you style your submit button with `button[type=submit]` you are sure all submit buttons in your app look the same. There is no need to use classes. (I also never understood why it is common to add a 'table' class to a table.)

I think the main reason why most CSS files become such a mess is that style and layout are not separated.

Several reasons to use "primary" class for buttons – `button` is just one of the elements that gets used with button styling, but there might also be `a`, `div` or `input` tags that look like buttons, depending on the use (or source).
The key point in this article is not Tailwind.

It is the endless susceptibility by software developers to marketing campaigns.

There are real costs to the use of any tool and these are completely glossed over by someone looking to sell you goods or services.

Here are some very real costs: technical attrition, maintenance, and training.

Technical attrition: Perhaps some people would use the terms amortization or depreciation, but I consider software to be in a separate class than intangible or tangible assets, the point being, there is a rather short shelf life for almost any technical product. CSS isn't going anywhere but tools like Bootstrap will come and go.

Maintenance: The old version of a CSS wrapper will stop receiving support and require upgrading to the new version which will take significant time and effort.

Training: There are orders of magnitude more individuals that know CSS than know Tailwind and they will be less productive for a given period of time.