> I got curious about what writing more semantic HTML would feel like.
I've been teaching semantic HTML / accessible markup for a long time, and have worked extensively on sites and apps designed for screen readers.
The biggest problem with Tailwind is that it inverts the order that you should be thinking about HTML and CSS.
HTML is marking up the meaning of the document. You should start there. Then style with CSS. If you need extra elements for styling at that point, you might use a div or span (but you should ask yourself if there's something better first).
Tailwind instead pushes the dev into a CSS-first approach. You think about the Tailwind classes you want, and then throw yet-another-div into the DOM just to have an element to hang your classes on.
Tailwind makes you worse as a web developer from a skill standpoint, since part of your skill should be to produce future-proof readable HTML and CSS that it usable by all users and generally matches the HTML and CSS specs. But devs haven't cared about that for years, so it makes sense that Tailwind got so popular. It solved the "I'm building React components" approach to HTML and CSS authoring and codified div soup as a desirable outcome.
Tailwind clearly never cared about any of this. The opening example on Tailwind's website is nothing but divs and spans. It's proven to be a terrible education for new developers, and has contributed to the div soup that LLMs will output unless nudged and begged to do otherwise.
you're unfairly conflating things and putting the blame for a lack of care or understanding on tailwind vs on the dev themselves. nothing about tailwind forces you to build inaccessible or "div soup" apps
can tailwind be used poorly? absolutely. but that's true of any tool
i've been writing CSS for ~20 years and am quite capable with it, having used CSS, Less, SASS/SCSS, Stylus, PostCSS etc. the reason i have settled on Tailwind for the last few years is precisely because it enables me to build more robust application styling.
tailwind frees you from having to spend excessive time building abstractions of styles/classes that will invariably change. placing the styles directly into the markup that is affected by it reduces cognitive load, prevents excessively loose selectors affecting styles unintentionally and really aids in debugging. jumping into codebases with bespoke css frameworks is always more complex and fragile than a tailwind codebase for anything but the most simple sites/apps
add to that the ability to have consistent type, color and sizing scales, reduced bundle sizes, consistency for any developer who knows tailwind and a very robust ecosystem (and thus llms are very familiar with it) and tailwind is a really excellent choice for a lot of teams
tailwind is like most tools; it can be used well or poorly depending on who is using it
> tailwind frees you from having to spend excessive time building abstractions of styles/classes that will invariably change.
Abstractions like a hero image, a menu, a headline? Sure, it's easy to overthink things but most of the time, it's not that complex.
> placing the styles directly into the markup that is affected by it reduces cognitive load, prevents excessively loose selectors
In my opinion, it's the opposite. Besides the obvious violation of DRY and the separation of concerns, inline CSS can't be cached and it creates a lot of noise when using dev tools for debugging. It actually increases cognitive load because you have to account for CSS in two different locations.
Lots of people use Tailwind because they don't want to deal with the cascade, usually because they never learned it properly. Sure, back in the day, the web platform didn't provide much built-in support for addressing side effects of the cascade, but now we have @layer and @scope to control the cascade.
Tailwind uses a remnant of '90s web development (inline CSS) and built an entire framework around it. I get why it appeals to some people: it lets you use CSS while not requiring an understanding of how CSS works, beyond its most basic concepts.
Your framing assumes incompetence across the board which is unlikely to be true for a framework of its popularity. Consider instead competent people are working on projects with different needs and they’ve recognized there are trade offs to both approaches and still decided Tailwind makes sense in their situation.
I disagree with that conclusion. I see tailwind as a cleaner more succinct version of css that is much easier to manage and add features too.
Sure it’s not as dry, but I’ve been bitten in this regard because css framework and templates are so intransparent, preventing me from simply changing padding or margin.
CSS is too detailed and too verbose. Frameworks like bootstrap are too high level and don’t give enough control. Tailwind hits the sweet spot whilst allowing me to be detailed if I want to. It allows me to just get it done.
To be honest, CSS had the cascade but also had horrible tools for actually managing the cascade for a long time.
If CSS had nesting, variables, media queries, the other nice selector queries like :has, and modules out of the gate, we likely would have not needed much of the tooling like tailwind that eventually got built to manage it all with less boilerplate. We built the tools because even when these features rolled out they came in fits and starts so you couldn’t adopt it without polyfills and whatnot.
I’ve also been writing CSS professionally for nearly 20 years and am a big fan of tailwind.
The ergonomics in my day to day work are quite nice. To me, the better boundary of abstraction shifted to components, rather than the html/css/js “separation of concerns” that some of the older folks still like to parrot.
However, take a look at the markup and styling for the https://maps.apple.com/ web property.
I can’t deny that it’s quite beautiful and easy to holistically understand. Especially when it comes to the responsive styling—which is when I tend to find tailwind most awkward.
It’s my favorite example of “traditional” CSS structure in recent memory that has given me some pause when it comes to Tailwind.
If a tool’s design makes it easy to cut myself, the response is not “people have been cutting themselves for years”.
There is such a thing as the ergonomics of the tool. Yes div soup has been around a long time. But also yes, Tailwind makes the wrong approach the easy one.
It’s ergonomics encourage adding div elements to support styles. It’s the core design loop.
You’re conflating “forces to” and “ergonomically encouraged”.
> can tailwind be used poorly? absolutely. but that's true of any tool
Can tailwind be a useful CSS framework? Absolutely, but that can be said of any of them.
Which is precisely why it makes sense to point out it's unique flaws, so that people can make an informed decision as to what works best for them.
If you have some unique feature to tailwind that you think makes it better than the rest, you should share that.
Everything you have listed is also accomplished by all the other CSS frameworks, so it almost sounds like tailwind is simply the main one you have experience with.
> Tailwind instead pushes the dev into a CSS-first approach. You think about the Tailwind classes you want, and then throw yet-another-div into the DOM just to have an element to hang your classes on.
But this isn't a unique flaw for Tailwind. I've been coding with CSS since the late '90s and seen plenty of people throw yet-another-div onto the DOM just to have an element to hang their classes on. Done so myself plenty of times, too.
People have been complaining about div soup for years and years before Tailwind ever came along.
Plus I'm coding with Tailwind now, and almost never think about my classes before my HTML. Nothing about Tailwind in particular encourages you to do so. So I'm quite confused how this is a unique Tailwind flaw.
I’ve mentioned this before here, but originally I was against Tailwind because it breaks the Cascading part of CSS, however I think a lot of websites lately work better with “locally scoped” styles as there are just so many different components that many things just dont need to follow a global style sheet. So now I usually reach for tailwind first, unless is a relatively simple vanilla html site
Global styling done messily can override local styling. It’s the hardest kind of problem to reason about!
I do think that stuff like bootstrap is generally good at avoiding this but it only takes a handful of improperly scoped high level CSS rules to cause awkward hard-to-fix pain much later on.
Global state is bad because it makes it hard to reason about your system. The global state can affect any part of it, or, focusing on the inverse which is probably better applied to global styles, any part of your system can depend on the global state.
It's also weird to say "global styles are not mutable" - you're right, they're (generally) not mutable, at runtime. But they are mutable in the sense that your developers (you, or your colleagues, or someone in 3 years maintaining your code) can mutate them, and if large parts of your system are implicitly dependent on the CSS cascading properly and so on, then those changes can have unintended consequences.
Of course, that can also apply to tailwind, to some extent. A developer can change a class (custom or otherwise) or the configuration - but at least it is very clear what is being changed and what parts will be affected (just grep).
With CSS unintended consequences are always problems of scoping things better. If I give semantically meaningful CSS classes to my semantic HTML and scope my rules to apply to their intended place in the pages, then unintended consequences don't happen. If I roll like: "Oh, I want this list to look differently, lets make a global scope ul/ol style!" then I am asking for trouble later on. When I write a CSS rule, I should always be thinking about the scope and whether my rules are truly something universally applicable to that scope.
Proclaiming professional advice on the internet should compel you to bother using proper caps and punctuation. To your point - you are addressing the symptoms of bad understanding of CSS and it's relationship to the DOM. While tailwinds is a useful tool, it's not particularly special.
> you're unfairly conflating things and putting the blame for a lack of care or understanding on tailwind vs on the dev themselves. nothing about tailwind forces you to build inaccessible or "div soup" apps
> i've been writing CSS for ~20 years and am quite capable with it, having used CSS, Less, SASS/SCSS, Stylus, PostCSS etc. the reason i have settled on Tailwind for the last few years is precisely because it enables me to build more robust application styling.
I think herein lies at least part of the problem of the web these days: Most websites don't need to be applications, and are needlessly made to be applications, often even SPAs instead of simply being mostly informational pages, in turn putting different requirements for styling onto the project.
> [...] jumping into codebases with bespoke css frameworks is always more complex and fragile than a tailwind codebase for anything but the most simple sites/apps
There is no need for frameworks. Well structured and scoped CSS can handle it all.
> add to that the ability to have consistent type, color and sizing scales, reduced bundle sizes, consistency for any developer who knows tailwind
What if not that does CSS already offer? I don't see how normal CSS does not already do that. No additional thingamabob needed.
> very robust ecosystem (and thus llms are very familiar with it) and tailwind is a really excellent choice for a lot of teams
Tons of ready-made stylesheets out there to use for teams. What more of an "ecosystem" do I need to style a web page? Why do I need an ecosystem? Is it not rather a tailwind self-induced need?
> nothing about tailwind forces you to build inaccessible or "div soup" apps
Totally agree. I feel like this was more a by product of React. Not that React forced this either, but it felt like the rise in both went hand in hand for some reason.
While I think it's true that none of the current top FE technologies force the div soup, they don't discourage it either. It would be nice if what ever FE technologies catch on next did try to encourage better practices around things like accessibility. Make the path of least semantic HTML the path of least resistance and allow people to fall into the pit of success, ya know?
Nothing about programming forces anyone to do anything.
That's never been a valid argument to dismiss criticism. It wasn't with Dreamweaver, any it wasn't with visual basic, and it isn't with Tailwind.
Patterns matter. Best practices matter. Path of least resistance matters. Those are all choices you make when you develop a CSS framework. Some of those choices are good and some are bad.
If none of those things mattered, them choosing a CSS framework would not matter at all.
React encouraged this for years by requiring a single parent element being returned from all components. They also showed a div as the option of choice.
They fixed this later with Fragments but the damage was done.
While I agree I do think there's some "aspiration of purity/correctness" in your approach that I've long let go of.
I look at the royal mess that is HTML/CSS/JS as a necessary evil, required when we want to target browsers. To me it's "just the presentation layer".
In my work I put a lot more emphasis on correctness in the db schema, or business logic in the backend.
When it comes to the messy presentation layer I prefer to write a little as possible, while still ending up with somewhat maintainable code. And for this Tailwind fits the bill really well: LLMs write it very well, new devs understand it quick, and it's quite easy to read-back/adjust the code later.
I 100% agree a Tailwind project is not the best way for a new dev to learn HTML/CSS. But then I prefer the new dev to focus on great db schemas, intuitive APIs, test-able biz logic, etc. Fiddling with the mess that's HTML/CSS is not the place where I consider human attention is best spent on (or where developers pick up skills to become much better developers).
This isn't about "purity/correctness" it's about the real experience of a blind person. Accessibility means caring about the HTML.
Your comment only mentions developers as the audience of HTML authoring, as opposed to users, which is a common attitude and the core problem with Tailwind.
Knowing the DOM structure is properly engineered is the gateway to an entirely compliant end product out the gate. Leaves pros looking for the "difficultly level" on SEMRush after maxing it out.
I use Tailwind and have all kinds of "screen reader" directives in my templates.
Not sure if it helps, but if we get our first blind user I will gladly make some admends to make it more usable for them.
It seems that Tailwind is now blamed for the mess that is HTML/CSS. Tailwind certainly allows for accessible designs; it may not be the ideal solution, sure, but what we aim for is "good enough".
> but if we get our first blind user I will gladly make some admends to make it more usable for them.
Isn't this slightly backwards? Why would blind users sign up if the platform isn't usable for them in the first place? It has to be usable for them for them to become users :)
> Not sure if it helps, but if we get our first blind user I will gladly make some admends to make it more usable for them.
I have heard "we don't have blind customers" argument many times before. Apart from ethical issues that this raises, ADA requirements, technically, don't care if you have blind users or not. Accessibility is still required...
It's a great way to make an easy $10-30k as a law firm too when you sue them. Especially when you have definitive statements like "we purposely don't care about a11y until we get sued for it."
It's not just blind people, but also people with reduced eyesight. As I'm getting older, I really appreciate good contrast and the possibility to zoom in without breaking the layout.
People always forget they're one unlucky event from losing their sight, hearing, dexterity, or more. Hell, like you say, even aging's enough to do it. Accessibility suddenly becomes a lot more important when you're on the other side of the "not a priority" talk.
If Tailwind lends itself to using pixels instead of relative units for things that should be relative (like font-size, line-height, etc.), that's a problem.
For those users, the HTML elements matter less unless they're savvy users who have custom user stylesheets to selectively adjust the appearance of content instead of changing everything on the page by zooming (e.g. make links, buttons, paragraphs, list items bigger and/or a different font or weight).
if we get our first blind user I will gladly make some admends to make it more usable for them.
Not good enough. You have to be accessible before it is needed in order to avoid legal liability.
And how do you expect to get a blind user if they already cannot use your product?
None of the doctors I build web sites for are currently blind. I know this because I talk to them regularly. But I still build the web sites for the future, when HR might hire a doctor or nurse or other person who is blind, or partially sighted, or has trouble with their muscles, or has difficulty distinguishing colors.
Doing the right thing isn't that hard. Not doing it is just lazy.
You call it lazy. I call it "focus" or avoiding pre-mature optimization.
I find the "legal liability" claim hilarious... I do better than 95% of the web: as I said I HAVE some screen reader directives (just did not test it), and labels to make the app more accessible.
Is this to be read that disabled people and their needs, or more directly from the replied-to comment, "doing the right thing", are not a focus of yours, flossly?
A former coworker of mine opened a meeting saying "we are so good, we care about accessibility". I had been complaining for months and finally a customer had said "we won't buy your product unless it complies to the law".
We use tailwind and are capable of building accessible websites without any issue. People could make all the same mistakes with CSS for accessibility. It’s the not knowing how to make accessible content that leads to inaccessible content, not the tool you use to implement the styling.
Sounds like you're kind of just talking your book though. Person who makes accessible sites suggests you need an accessible site. Blind people aren't the only ones who might need modifications. You could have an infinitely long list of adjustments for all kinds of disabilities, and tell me I'm lazy for not doing each of them. Why are blind people special?
You are lazy for not doing accessibility adjustments, because accessibility isn't for blind users. It's for the deaf ones, the ones with poor eyesight, the ones with mental deficiencies, the ones with motor issues like Parkinson's, the ones browsing your site shitfaced at 4AM, and so on and so on.
Accessibility isn't a checklist to cover your ass for a percentage of the population: it's for everyone. It literally makes your website less shit. You slapping an aria-label doesn't fix things.
Every moment you spend doing accessibility is a moment you spend not doing other things. You could argue it has a high RoI to do accessibility, fine, but that doesn't make it lazy _not_ to do it. Maybe I have even higher RoI/EV stuff to be doing.
Yeah, thats explicitly what I'm saying so I'm not sure it needs repeating. That has very little to do with it being lazy though, is the point.
We were already implicitly discussing RoI when we were talking about 'legal consequences' above. This is how people decide between alternatives, generally.
Picking subsets of customers to focus on is a totally standard part of running a startup or company in general, so this is not really news or any kind of threat.
You might as well tell me the suburban moms are not going to buy my developer tool because I've personally slighted them with the branding. Why would I care? I made my decisions knowing this.
In fact ditching low RoI customers is incredibly common and good startup advice.
You're just expressing a normative view here, it's not very interesting or informationally-dense. You care about accessibility more than I do. That doesn't make not doing it 'crap'.
I mentioned blind but there’s lots of others. Folks sitting a desk whose eyesight are getting worse and are scared to say so for fear of losing their job, for example. This happens.
Side note: if you aren’t deliberately choosing semantic elements and instead dropping aria attributes onto a bunch of divs this is an anti-pattern.
What does Tailwind have to do with accessibility? Most significant HTML markup is block level elements. The CSS is completely orthogonal.
I feel like old-school frontend devs bring up accessibility as a kind of bogeyman.
It reminds me of the myth that CSS style X or Y breaks accessibility "because screen readers expect semantic CSS classes". Zeldman (of A List Apart) promulgated that disinformation for years, until someone actually told him screen readers don't work that way. 90% of people who use a11y as a rhetorical cudgel have never actually used AT themselves.
I'm not familiar with that old tale about Zeldman. It's true that assistive technologies don't know about CSS class names but CSS absolutely can affect a non-sighted screen reader user's experience.
I don't use Tailwind so I don't know if it makes it easier or harder to do the right thing when needing to hide something from everyone or only visually hiding something. Because it's CSS, it can't take care of only hiding something from assistive technologies.
But why would I spend any time mastering this skill when we have AI now?
Disability software that uses both the markup and the on-screen visual for decision making is likely imminent and would render most of this no longer necessary.
Claude Cowork is already doing navigation and web browsing by screenshot showing this is possible.
Treating markup and styles separately is great, in principle, but you'll always need additional markup for certain things. We knew this going back to the early 2000s.
There is nothing about Tailwind itself that forces you to use divs and spans instead of the appropriate HTML tag.
Documents and interfaces are different. Tailwind makes a lot more sense for interfaces. You can use Tailwind for the interface and scoped HTML selectors for other content.
Tailwind is around 4x faster and has practically no overhead compared to writing a complex CSS codebase. Whatever you think of it, this is always a benefit in its corner.
I always feel like the distinction between interference and document is missing in these types of discussions. Often times they're as different as native vs web dev and if you don't realize that then you're arguing about totally different things and nothing will make sense.
As someone who wrote CSS for 20 years and who was against using Tailwind because of “principles” I must say that Tailwind is just awesome. Every minute spent trying to make sense of the structure past you or your colleagues came up with is a minute that could be spent on something more important.
Every time someone says that Tailwind sucks, it’s like hearing the old me speak.
Same here. It’s super weird take to me now. Maybe if you’re just writing plain HTML and CSS tailwind would be worse, but assuming there’s a component system you’re going to be just fine. The cascade of CSS is such a foot gun. Localized styles work great and tailwind abstracts away hardcoded values with relative ones
It's unfortunate Inverted Triangle CSS (ITCSS) isn't more popular. Instead of resisting the cascade, it embraces it and makes it work for the developer.
The summary: write your CSS in specificity order [1]:
/scss/
├── 1-settings. <- global settings
├── 2-design-tokens <- fonts, colors, spacing, etc.
├── 3-tools <- Sass mixing, CSS functions, etc.
├── 4-generic <- reset, box sizing, normalize, etc.
├── 5-elements <- basic styles: headlines, buttons, links
├── 6-skeleton <- layout grids, etc.
├── 7-components <- cards, carousels, etc.
├── 8-utilities <- utility and helper classes
├── _shame.scss <- hacks to be fixed later
└── main.scss
ITCSS basically does away with specificity wars in a CSS codebase. Usually the only place !important is the utility layer.
This is brilliant, I was not aware of ITCSS. Thank you for sharing! The link you shared fits my brain a lot better than pure BEM/CUBE, which works but always felt weird and uncertain to my style. Sprinkling a bit of BEM on top of ITCSS feels just right. shame.scss is the snarky cherry on top. Thanks again, you have enlightened at least on person today! :)
How are ARIA roles/attributes bad for accessibility?
Sure, if there is a HTML element that works then use it, but not every UX pattern is expressible in HTML without specifying roles/attributes (e.g. tabs [1]) and not all browsers support recent HTML elements/attributes (such as using details/summary for accordions).
ARIA patterns [2] has a list of examples for UX components and their examples specify/use ARIA roles/attributes.
WAI's APG patterns exist to document how ARIA attributes should work, they don't advocate for them to be used in place HTML elements. They also don't test to confirm that they actually work in browsers or with assistive technologies (some specific patterns are fine). For web developers, they're helpful for documenting expected keyboard interaction support and other norms.
Are you still coding to support Internet Explorer? All browsers have supported details/summary since an Edge switched to Chromium in 2020.
I agree. I don't really like Tailwind, nor similar CSS frameworks. The whole idea was to separate styling from HTML, and Tailwind is putting it back into HTML through the backdoor. It's just a way to do styling from your HTML without having to touch the CSS, by inserting styling info in your HTML. That's exactly what we were trying to move away from.
I agree with the criticism of tailwind. IMO any good criticism warrants at least an opinion on what should be done instead or some corrective or remedial patterns.
there is a reason why tailwind got as popular as it is today. And it only highlights the gaps in either what HTML and CSS provide for the task at hand or the difficulty in that approach. This must not be lost in any criticism.
another observation is none of technical user interface decisions or discussion emphasis on the tree data structure that is inherent to every major user interface rendering mechanism relevant today. there are inherent benefits and drawbacks of it being a tree structure that neither of the developers nor the framework leverage. when thought of as a tree, it benefits from adding certain constraints and naming conventions that allow more artistic expression using just HTML and CSS that I have not seen tailwind or any other framework encourage
> HTML is marking up the meaning of the document. You should start there. Then style with CSS. If you need extra elements for styling at that point, you might use a div or span.
IMO this is the fundamental problem with HTML and CSS. You'll always have some part of the styling in the HTML due to needing extra divs and spans. At that point splitting the styling outside into the CSS splits your attention and Tailwind "solves" that by moving everything back into HTML.
Note that I don't like Tailwind, but I would rather have a way of styling that does not need to rely on the existance of extra divs and spans to work.
You're not wrong, and I mostly agree with you. I die inside when I see the div soup that a lot of sites have become. However, I think there is value in being able to have the important parts of CSS merged into the HTML a bit. Where that line is, is certainly up for debate (and I don't have the answer), but I've found a lot of my tailwind sites are more readable to me than my pre-tailwind sites, often because I don't have to context-switch and open a different file to be able to reason about the styling on an element. For big stuff the second file can be nice, but there's a lot of style tweaking that is great to be able to do right there in the HTML. Tailwind does really lead you to ignore the css file though (or keep it highly minimal), which I agree is becoming an anti-pattern.
The "open a different file" reasoning piece is a common pro-Tailwind statement and I do see the upsides.
I think that upside became more prevalent in the reusable components era, whereas previously CSS was targeting an entire HTML file (and thus the reasoning was more like SQL query than "this one element's styling").
With LLMs I think this upside is much smaller now though.
> With LLMs I think this upside is much smaller now though.
With LLMs Tailwind wins. Because it's a very restricted set of classes. With regular "separation of concerns" CSS, LLMs will happily just pile on more and more and more CSS because they can't really analyze the code that's already there, and will miss and re-create huge chunks of CSS. Or write increasingly hyper-specific CSS to fix reported issues.
Anecdotally: in a side project I now have 10k lines of "pure" CSS generated by LLMs on top of Tailwind. The web part of the app is ~20k lines (not all of them are rendering anything on screen). No idea how to fix it :)
It seems that everyone is forgetting the web inspector as a tool for designing web pages. You can tweak properties and styles in a live environment, and then transfer your preferences to the css files.
I don't have to context-switch and open a different file to be able to reason about the styling on an element
Unless you're coding on a VT100 terminal, you just put the HTML in one window and the CSS in another. Subdivide as necessary, or as your monitor space allows.
Heck, we were doing that back in 1989 on IBM PCs with MDA displays.
If your CSS is so out of control that you can't wrap your brain around it, it's time to refactor or split into individual CSS component files.
But more seriously, I should have been more specific. Having the second file open in a split pain isn't that big of a deal, but having to navigate and find the right selectors can be. If class names are used well then it's pretty easy to find those, but my experience with that is riddled with inconsistency when I'm not the one who wrote it.
On that note, it's also much easier to review CSS changes in pull requests when they are right in line with the file. Otherwise I have to do the same lookup to find the corresponding HTML, and reason about whether the selector could potentially be grabbing things that aren't obvious, etc.
Even if one were limited to a single window, I’d hazard to guess that most modern web frameworks and languages have some degree of templating and pre-processing capabilities, which let you have logically contained files with HTML and CSS that decompose/render into separate files.
I’ve made setups like that on a number of projects (ASP.Net & various .Net web frameworks). keeping clean separation of concerns, proper cascading, but also a simplified development experience where the ‘component’ file contained all relevant code, markup, and local styling.
25 years ago, I was appalled how Microsoft Frontpage could transform a very simple word document (with little formatting) into an utterly indecipherable mess of HTML that rendered correctly.
With very simple transformations, I could paste the text of the document into notepad and add just a few heading tags for the same rendered result but a much more understandable source.
CSS had a lot of promise for simplifying the HTML content, but the world tried its hardest to prevent that.
Now we have multi-megabyte monsters for simple webpages (before even counting graphics).
HTML is marking up the meaning of the document. You should start there. Then style with CSS.
This is precisely how I do it.
Code that generates HTML. Once I can see all the content on the screen in some kind of Netscape Navigator 1.0 nightmare, then I go back and add styles to make it look pretty.
It's not hard. It just requires thought and planning.
(The best planning tool I've found is a pencil and grid paper, not the web design SaaS-of-the-moment. However, it's surprisingly hard to find good pencil sharpeners these days.)
> Tailwind instead pushes the dev into a CSS-first approach. You think about the Tailwind classes you want, and then throw yet-another-div into the DOM just to have an element to hang your classes on.
To be fair plopping a `div` everywhere started way before Tailwind. I blame React and the mess that is CSS in JS for this.
Divitis was a thing long before React came along. It was a common solution to styling problems even in the jQuery/Dojo days. Getting stuff to look similar across IE6 and FF before CSS3 relied heavily on divs.
> Tailwind instead pushes the dev into a CSS-first approach. You think about the Tailwind classes you want, and then throw yet-another-div into the DOM just to have an element to hang your classes on.
I wholeheartedly disagree. That mindset is not caused by Tailwind, but by being ignorant.
You can perfectly create an HTML document with semantic meaning and the add Tailwind just as any other CSS framework or pure CSS to it.
And DIVs do not carry meaning, they are specifically to add functionality or styling, so you can throw in as many as you like. Using them abundantly isn't good style, but the way you make it sound that they're evil isn't good either.
The HTML spec says divs are the element of last resort. This issue isn’t that they’re bad. The issue is they are reached for far too quickly.
Also if you think massive numbers of nested divs don’t have a performance impact in the DOM when reusable components are nested (because “styling”), you’re wrong.
> The HTML spec says divs are the element of last resort. This issue isn’t that they’re bad. The issue is they are reached for far too quickly.
The problem is that HTML gives us very few tools to do anything useful. And you can only push certain elements so far. Div and span are generic elements with no semantics attached. You want a layout? Div. You want a change to a part of text? Span.
The only reason they are called "elements of last reserve" because it's only true if you remember that HTML is, has been, and forever will be a tool to display static text, badly. That's why you have article, section, p, and other text-oriented elements. But the moment you want something beyond that? Welcome to divs.
> HTML is marking up the meaning of the document. You should start there. Then style with CSS. If you need extra elements for styling at that point, you might use a div or span (but you should ask yourself if there's something better first).
> Tailwind instead pushes the dev into a CSS-first approach.
You're putting the cart before the horse. Or forgetting either the cart or the horse. Tailwind doesn't force anything. And "semantic HTML" or "semantic CSS" are not really a thing, and have as much bearing on how many divs a page has, as Tailwind.
And the reason is simple: there's literally nothing else in HTML than divs and spans. The amount of usable primitives is absolutely laughable, and trying to combine them in any useful manner results in as much soup with Tailwind as without Tailwind.
> since part of your skill should be to produce future-proof readable HTML and CSS that it usable by all users and generally matches the HTML and CSS specs.
Which part of Tailwind isn't readable, isn't future-proof, or doesn't match HTML and CSS specs?
How is "px-4" none of that, but ".ytp-big-mode.ytp-cards-teaser-dismissible .ytp-cards-teaser-label" (Youtube's CSS) or ".swg-button-v2-light[disabled]" (Washington Post) or "legacy-popover--arrow-end-bottom:after" (Spotify) are?
> The opening example on Tailwind's website is nothing but divs and spans.
Oh no! And what are the opening examples on any of the "proper pure-as-god-intended CSS" sites?
What I should've said in my hastily written comment should have been: "and other implementations of the same (or other) functionality isn't divs and spans?"
I think my only true criticisms for Tailwind example would be:
- should've probably used h2/h3 for card titles. Though this is dependent on where and how the card is used
- should've done more with the meta (number / date). But in a real world these would probably still be spans (for example, to mark them in different colors etc.)
HTML doesn't have a card element. So when you create one, you... use whatever's available. And divs and spans in HTML+CSS literally exist to manipulate layout and text.
I feel like this is a bad example because “card” is a presentation thing, not a content thing. On a social media site, you can have cards with submissions, in which case <article> is the proper tag – and “card” is just a way to style the submission, so it deserves to be a class.
> I feel like this is a bad example because “card” is a presentation thing, not a content thing.
It is both, and herein lies the problem with HTML and the quest for purity. The content you display in a card differs from the content you display in a different context.
The world is filled with "bad examples".
> so it deserves to be a class.
I guess I haven't looked at <article> docs since it was introduced many years ago [1]. Talk about "semantic" lol. The entire definition has been twisted and turned to be nearly indistinguishable from a <div> element. TIL that "product card" is an "article" [2].
I guess the reason why people use divs is that they may look for a corresponding semantic element, but don't see it in the list, and don't look into technical details, so reach for a generic div.
CSS is badly designed and uses a confusing, separate DSL with arbitrary rules designed before the Internet was widely used, before web apps existed, before smartphones etc
It's trash and throwing it out is good. Not learning it is good. Tailwind is a solution to a real problem.
More importantly, AI is good at it already and it's unlikely humans will need to understand HTML/CSS at all within a year or two. There's no reason to spend time learning how the gears work, just put the cover back on
“CSS is bad”
Why?
“Because reasons.”
Care to explain?
“No need to learn it anymore, my AI can do it for me”
Okay, but why is it bad to learn it
“Reasons”
>It's trash and throwing it out is good. Not learning it is good. Tailwind is a solution to a real problem.
Yup. Spent a decade of my career writing CSS every day, I was what you would call a "guru" and have written easily hundreds of thousands of lines of it over the years. Haven't touched a class or a stylesheet in nearly a year now, and probably never will again. Good riddance.
Same for me, I started web development with Netscape and IE5, and all the browser specific CSS declarations and media queries, checking on so many different browsers. Then all the trouble with positioning by inline blocks/float, then flex boxes. Now AI does all the HTML structure and CSS. Never have to do it again.
She writes from a place of vulnerability and honesty. Most people write to sound smart and she writes to say "I don't know it all but there are some things I discovered I want to share." I almost feel like she writes to share things with people she loves, even though she doesn't know them directly.
She spoke alongside Randall Munroe at the last Strange Loop (RIP). Some people waited to talk to him afterwards, but I waited to talk to her. I don't think she got my joke that she should rewrite her bash scripts into perl and for that I'm truly sorry.
> I almost feel like she writes to share things with people she loves, even though she doesn't know them directly.
Thank you for articulating this!
I'm not Julia, but I'd just like to put down here that this is pretty much my philosophy for public speaking/giving presentations, and I have been trying to instill it in some coworkers who struggle with presentations. It's a great privilege to be able to convey to one's peers and loved ones things that you're (likely) a bit more familiar with than they are and which may help them with some matter.
CSS Modules are a simpler solution to cascading problems. They create unique class names, so your classes don't clash [1]. And they don't have the two main downsides of TW, which are readability [2] and tooling. Tooling for debugging and experimenting interactively with Chrome and FireFox DevTools.
One thing that has always struck me about Tailwind is that practically every argument its proponents use more or less boils down to “I never learnt CSS beyond a junior level”. It’s super common to hear Tailwind advocates say things like “Without Tailwind, we would just have one big disorganised CSS file that always grows uncontrollably and ends up with loads of obsolete stuff in it and !important everywhere! Tailwind is so much better!”.
CSS is a skill just like any other technical skill. If all you do is learn the bare minimum so you can bodge things until you get something that looks right, then your ambitions are going to outpace your ability to keep things organised very quickly.
It's worse than that; the common arguments for Tailwind literally derive from total ignorance of how CSS is made to work, and a disposal of guidelines that developers would worship in any other context (i.e. Don't Repeat Yourself).
It's really frustrating to be talking with someone about Tailwind and CSS, and realize that not only do they not know what "cascading" means, they never even considered the concept might be useful in the context of a stylesheet.
Tailwind, JS-in-CSS, and the like have become popular because they work well with the modern corporate UX workflow. A Figma component has a certain set of styles, you apply those same styles to the corresponding React component.
And none of this really violates DRY, your unit of reuse has shifted from a CSS class to a framework component. There's nothing precluding you from using an approach like DaisyUI if stock Tailwind has too much repetition for your taste.
> A Figma component has a certain set of styles, you apply those same styles to the corresponding React component.
This is what CSS classes were made for. Of all of the arguments in favor of Tailwind, this is the one that drives me battiest. Say what you will about CSS, but "give a name to a re-usable set of styles for a component" is pretty much as fundamental as you can get.
> And none of this really violates DRY, your unit of reuse has shifted from a CSS class to a framework component.
Sure, sure. except for the inline styles everywhere. And the fact that everything is literally being repeated all over the place. But other than that, no repetition!
> There's nothing precluding you from using an approach like DaisyUI if stock Tailwind has too much repetition for your taste.
That brings with it the problem of naming a thousand things in a consistent way that everyone on your team needs to understand and remember, otherwise you end up with tons of duplicated classes, parallel systems, and bike shedding. Have we, as an industry, not felt this pain often enough yet? Do we really need to keep banging our head against the wall to figure out it does hurt?
> Sure, sure. except for the inline styles everywhere.
There are no inline styles when using Tailwind. There are references to variables from the design system.
> And the fact that everything is literally being repeated all over the place.
If you find yourself repeating the same sequence of classes, it's time to create a component in your frontend framework if you use one, or a Tailwind utility class. And even if you just copy-paste the same class strings all over the codebase, transport compression will eliminate that pretty much entirely.
> That brings with it the problem of naming a thousand things in a consistent way that everyone on your team needs to understand and remember, otherwise you end up with tons of duplicated classes, parallel systems, and bike shedding. Have we, as an industry, not felt this pain often enough yet? Do we really need to keep banging our head against the wall to figure out it does hurt?
Of course. It's obviously better to have 10,000 different names that are all loosely, but not exactly the same as the CSS property they're trying to represent.
I for one do not understand what is so difficult about making a team internal decision about how some "component" (here in quotes, as I am actually thinking of an HTML subtree with specific purpose somewhere on an HTML page) is going to be named, and then give it that name as CSS class. Are people never talking with each other? Are people unable to grep a code base, before making up a new name? And how many similar but not same purpose things do you have on your pages, that this becomes a serious problem? Or is it just a discipline problem? People can name hundreds of useless OOP abstraction classes, but cannot be bothered to think of a good name for a "component" on a web page?
I mean, come on, there is usually tons of context and team internal language for the new thing to build and to talk about it, distinguishing it from the old thing that was already built.
And if that's too hard, then allow the design department to name the things they design and notify them about any clashes. They must have a design language anyway.
Premature optimisation doesn’t even fully express how absolutely ridiculously futile it is to try and make your website faster by having fewer CSS selectors.
It’s like my grandparents worrying about immediately switching off their LED ceiling lamps when they leave a room - meant well, but utterly meaningless.
There are a bunch of differences between Figma styles and CSS styles that prevent you from creating a 1:1 mapping: typography inheritance, spacing rules, and variant specificity to name a few.
Like yes, CSS by itself is extremely powerful, but I see no reason why you should feel beholden to use all of its features simply because they're there.
> Sure, sure. except for the inline styles everywhere. And the fact that everything is literally being repeated all over the place. But other than that, no repetition!
Well, instead of repeating inline class names everywhere, you end up with CSS properties repeated everywhere. Not really seeing the difference.
> Well, instead of repeating inline class names everywhere, you end up with CSS properties repeated everywhere. Not really seeing the difference.
Erm...what now? That's so off-the-wall that I can't even wrap my head around your meaning.
Are you trying to argue that because, say, a conventional CSS file has "border:1px" in multiple places, this is somehow equivalent to the Tailwind approach of making a "b1p" class that captures the same thing [1], and plastering it across your templates?
Because a non-abusive application of CSS would actually just put that border property in a semantic class like ".widget" or something, and sure, you'd have multiple "border:1px" declarations across all of your CSS files, but that's irrelevant, because you're not trying to reconstitute every style inline from pseudo-properties.
[1] I am making this example up for illustrative purposes.
Yes, that's exactly what I'm saying. You don't end up needing a semantic class like .widget since you likely already have a Widget component in your codebase. Essentially:
> but "give a name to a re-usable set of styles for a component" is pretty much as fundamental as you can get.
Yes. And as 30 years of CSS show, it's not enough.
> Sure, sure. except for the inline styles everywhere. And the fact that everything is literally being repeated all over the place.
It's not repeated all over the place, because in your codebase you have a single place where component A is defined. A single place where component B is defined etc.
I don't see you complaining about having to repeat the same CSS properties (padding, margin, display etc. + responsive styles + hover/disabled etc.) for half of the components when writing vanilla classes.
The common arguments against Tailwind usually derive from total ignorance of working with CSS on large scale projects with many team members.
And when this is pointed out you’ll usually get replies that just hand wave it away as not a problem, as if things like BEM were invented for no reason.
And the cascading thing is a nightmare even after years.
Whenever i have written CSS/TailwindCSS which was unproblematic to extend it was when i literally switch thinking to use least amount of properties and let the page flow.
Whenever i see tons of css i know it’s brittle and will cause hours of wasted time down the line to fix something which already should have been fixed.
The more experienced Tailwind proponents probably have better things to do than get dragged into yet another online flamewar :) I've done tons of CSS since the 90s before looking into Tailwind. After it clicked, I've mostly tried to avoid raw CSS. In a sense, you exchange one mess for another. Personally, I'd rather deal with a localized class soup than trying to make sense of overlapping, often contradictory, cascades of styles across multiple files. Both can be implemented cleanly, but I'd much rather clean up a Tailwind mess than a CSS one. And I find the development process much more enjoyable overall.
> Personally, I'd rather deal with a localized class soup than trying to make sense of overlapping, often contradictory, cascades of styles across multiple files.
That seems like a false dichotomy. I'm a huge fan of locality (both in software engineering and in physis) but you can also "localize" your styles by scoping them appropriately. (Modern frontend frameworks typically do that automatically for you at the component level.) There is no need to use Tailwind for that.
You're misunderstanding me. I never said you should build a framework. I said frontend frameworks already provide style locality out of the box, so there is no need to introduce an additional framework (Tailwind) for that.
You aren't wrong, but the _overwhelming_ majority of "full stack" devs I've worked with only know CSS at the most basic level and have little interest in learning it in depth. I myself have been programming for more than 20 years, doing web dev for almost 15, and I can't find the motivation to learn it well. There are too many technical skills to keep up with and CSS is pretty low down on my priority list. I would prefer to rely on specialists who are experts but companies aren't willing to hire dedicated front end devs.
Isn't Tailwind easy to understand when you look at the codebase, rather than putting in more effort to learn a pure CSS codebase? Isn't that part of the argument of Tailwind being easier to scale?
What exactly has changed about Tailwind in, like, years? There are a few more properties for new CSS features, a few convenience features (like size-x instead of w-x h-x for the same values of x), but other than that? If you've grown accustomed to the utility classes eight years ago, then disappeared under a rock until today, you should be able to continue working in an unrelated, Tailwind-using project immediately.
I think there is a wide spectrum of "wrapping SQL", and very likely not because someone doesn't know SQL well. Depending on the use case, it can be just the right solution or overkill.
Tailwind, on the other hand, attempts to address a different set of problems, but I am not getting into that here -- other comments have summarized it well.
Your comment is getting downvotes, not necessarily because you are wrong, but 1) CSS is indeed hard, complex and often confusing, which is partly why Tailwind exists in the first place 2) your comment points out some inconvenient facts, and people don't like that
Personally, I'm not sure from my own dives into it that I'd still insist on bare CSS in a professional codebase any more than I'd insist on plain DOM manipulation. And I do at least see Tailwind classes as being a little less of a DSL than other, similar tools. But while I'm not going to be a purist about it at a workplace, I both agree with you and have noticed a layer even beyond your point: that overreliance on these things leads to not learning HTML beyond a junior level.
It gets really easy to lean on class-based CSS and use a `<div>` for everything instead of ever learning what a semantic element is.
And that contributes to other bad habits, like writing a bunch of JavaScript to define behavior that could just be natively handled by your browser.
A weird personal irony is that because no employer has ever asked me to directly write CSS, what's actually made me better at CSS is JavaScript -- namely that my understanding of selector logic has improved a lot after picking up Web scraping.
I'm a fan of removing any dependencies on external libraries and writing my own solution from scratch, but there's a good reason why I decided not to do so with Tailwind: They offer an optimization for production that ensures that you never ship more than the bare minimum of CSS needed. This means you can keep your palette of color, spacing, and other options fully enumerated in `globals.css` and elsewhere, without worrying whether you're using all those variants in production. Moreover, if you're working within a framework, such as Next.js, this minimization step automatically happens when you build, without even having to worry about whether it's happening. This alone is a compelling reason, at least for me, not to migrate from Tailwind.
Also, I've never found any restrictions in Tailwind in using inline CSS that weren't readily navigable, or in implementing really nice responsive grids that handle different screen widths for instance using Tailwind's grid tooling. I definitely have solved each of the scenarios described in this article using Tailwind or a Tailwind-CSS combination, but it's true that they don't have grid-column-areas natively. Still, I haven't yet found that to be a significant restriction in getting responsive grid layouts.
I think the biggest issue with Tailwind is simply that it takes a long time to get used to reading it. We all learn that inline CSS is bad, globally scoped CSS is best, etc., and we get used to seeing clean simple HTML. Then we look at real-world code featuring Tailwind and it just looks so hard to read at first, especially because the lines are so long. I guess I just have been using it long enough that I've gotten completely used to the way it looks, but I do remember it took me a very long time to get comfortable with reading Tailwind. After a long while, I concluded that, for me, Tailwind really is more efficient and maintainable and even more readable, but it definitely took quite a bit.
One approach I've been really starting to enjoy is to use use Tailwind alongside scoped styles (in Svelte and Vue). This keeps template pollution minimal while still allowing for the conveniences Tailwind brings:
For me Svelte and LLM completely removed my need for Tailwind. Turns out I was using it primarily to avoid CSS collision, and (to me) more logical syntax, rather than the self-imposed constraints.
It bothers me that SMACSS is from 2011 and was completely ignored. That was the sane way to write CSS. Been doing that for more than 10 years and never had issues redesigning large applications.
Lately I've been enjoying Open Props[0]. It's a library of CSS props/ variables that helps structure a design system. I like it because it's CSS-first, so like OP experienced moving off TW, I've learned more CSS, and it works with the browser not against it. It also provides some sane defaults for anyone less interested in fiddling with precise cosmetics.
css applies attributes to objects via graph queries
the queries are tightly coupled to the tree. you must work hard to avoid scatter gun edits now. it doesn't make much sense to have attributes stores in a separate location to their use
it would be like assigning all of your instances attributes using decorators
Tailwind crazy adoption is something that makes me happy to nowadays be doing mainly boring stuff in distributed cloud systems and agents, instead of WebUIs.
I'm observing recurring patterns in Tailwind-only users: they learn a lot of non-transferable and bad habits, especially when the codebase scales up:
- Engineers never learn to properly use developer tools to debug CSS
- Components get gigantic bloated piles of classes that are not human readable
- Those gigantic piles of classes get logic in them, that often would have been easier to write as a CSS selector. Tailwind developers learn to write a JS ternary operator with a string of classes instead of ever learning how CSS selectors work
- Those ternary operators get too complicated. The engineers write object maps of Tailwind classes, or export consts of strings of Tailwind classes to use later. Those object keys and const names are what the CSS class names could have been if they just used CSS. They literally re-invent CSS classes, but worse.
- Tailwind classes can't be migrated. You can migrate CSS to Sass to CSS modules to Emotion CSS to etc mostly just by copying them over, because all of those are CSS (with some quirks). Tailwind classes are non-transferable
The happiest medium I've found was in an organisation of around 200 UI engineers: scoped CSS so that engineers can work with autonomy without colliding with other engineers, plus Tailwind for quick band-aid fixes.
> You can migrate CSS to Sass to CSS modules to Emotion CSS to etc mostly just by copying them over, because all of those are CSS (with some quirks). Tailwind classes are non-transferable
Tailwind classes are literally vanilla CSS classes. You can copy-paste their definitions directly
I'm so grateful that people are starting to see that Tailwind is insanity. Just write your class and your CSS as CSS. We don't need to pollute templates with endless hard to parse and reason about tailwind nonsense.
> I’m a lot better at CSS than I was when I started using Tailwind.
> I got curious about what writing more semantic HTML would feel like.
This is so relatable. In the beginning of my career, I used to add so many dependencies for things I did not know. But these days, I mostly work on removing dependencies because I'm a lot better at using the web platform. I treat the web platform and browser primitives as materials to build what I want rather than a blank canvas to paint things from scratch.
Vue single-file components (SFC[0]) still works this way.
<template>
<!-- Largely just HTML -->
</template>
<script setup lang="ts">
// JS/TS as you would expect
</script>
<style scoped>
/* Component scoped styles here */
</style>
Very clean, easy to understand, and (as someone who started hand writing DHTML) it still feels very much like DHTML with more convenience and modern affordances.
The only problem with Tailwind is its syntax. It is anti-CSS. It is confusing and takes time to get used to. Heck, people even wrote cheat sheets. That makes adoption wrong, because people install it just because everyone else uses it or because the current thing brings it in. A long time ago, before Tailwind, I was writing true, pure functional CSS https://www.fcss.club/manifesto, and I never came back to the old OOCSS or BEM style of structuring all the styles in a project. Components helped a lot with that decision. Today, functional CSS does not make sense only if your website is truly, truly simple. But if your application is more complex, FCSS outshines everything else: speed, rendering, simplicity https://www.fcss.club/syntax, and weightlessness.
I don’t believe Tailwind is inherently worse than pure CSS. If Tailwind had existed from day 1 on the web and you had learned it first you probably wouldn’t say this. In fact, if Tailwind had existed first somehow, and someone came up with CSS as we know it as a new revolutionary library, I’m not sure it would have succeeded.
Well, the old-school way was something like this: h1.font.size = 24pt 100%. Tailwind has its own syntax, so you need to learn it first, which adds extra cognitive load. For things like * { color: } in Tailwind, you have multiple options: text-color, stroke-color, etc. With FCSS, you simply have color--[colorname] { color: … }. It’s simpler and avoids unnecessary cognitive load.
I used Tailwind in one web based product and it was alright, but I'll never go back to using it.
The HTML bloat was really tough to deal with. I spend far more time in HTML than I'd like, and having more Tailwind classes than I do semantic HTML was really tough to look at.
I've settled on using vanilla CSS and applying styles per-page on an as needed basis. For example, include base styles (reset, primary theme, etc) and then include marketing styles (or: blog styles, dashboard styles, syntax highlighting styles, charting styles, etc).
It keeps each page light and minimal. Reading the HTML is easy. Styles stay consistent across any pages that share styles, etc.
IMO the killer feature of tailwind is that it lives alongside your React components so you keep things DRY, and you get a design system with type and spacing scales out of the box. It’s a form of constraint that helps create structure. But I think that makes it a victim of its own success. The tailwind spec becomes ever more complicated the more native CSS features it tries to include. I’ve seen tailwind incantations go way beyond editor wrap line
I still like it though. it’s one of those abstractions that actually helped me learn. I would go to the tailwind doc pages and see the underlying css of any class.
There were some other frameworks I got excited about: vanilla extract and stitches, both made by some really talented people. I wonder why those never quite got the same traction…
Because it’s a made up rule, not a law of nature. Having the styles inline on a component is very easy to reason about, it was just extremely impractical with CSS and the style attribute. Tailwind makes it easy, practical, and is actually well designed regarding its support for variables (ie. you can have a sane design system easily).
What you want to share/cascade is variables, not styles. Styling components makes it easy to make sure the styling of each component is isolated and doesn’t have unintended cascading effects. When working this way, using Tailwind is as much a good pattern than, say, CSS modules (which I like too).
The elephant in the room is AI. The frontier LLMs seem to prefer Tailwind by default. And they do a good job with it--likely because coupling style definitions to your HTML document structure is the least overhead way for an LLM to write code. The alternative--reasoning about a platonic ideal CSS structure, mapping the document structure to that an inferring how the layout will look, then juggling all that while making iterative edits--is a lot more "cognitive" work!
Tailwind makes sense when you can use React Components to build a reusable design system. It is annoying as heck to use if you don't have that superstructure.
To add: I dont want to go back to writing semantic css.. no thanks - I dont go in time-back machines. This is a mess.
Just using tailwind and anchoring around a design system like shadcn is just way easier for a team to align around than somebodys made up css language.
I just want to point out that you can use Tailwind inside your CSS with the `@apply` directive (not to be confused with the since abandoned CSS `@apply` rule). You write your CSS and mix in Tailwind instructions where it makes sense. Example:
@import 'tailwindcss';
p {
@apply text-justify;
@apply bg-slate-300 dark:bg-slate-800; /* Second rule just for colors */
display: block; /* regular CSS */
}
I used to be a big Tailwind hater because putting all those utility classes as inline styling into my HTML is a crime against nature. But this way I get the best of both worlds. Tailwind is really nice as higher-level building blocks and saves me from writing a bunch of media queries.
Even with components I prefer what Astro is doing: the component can have a `<style>` tag in which I can add my own CSS. When building the website Astro will know how to transform the CSS so it only applies to that component. This way markup and presentation remain separate even if they are within the same source file.
> Builders value getting the work done as quickly and efficiently as possible. They are making something—likely something with parts beyond the frontend—and are often eager to see it through to completion. This means Builders may prize that initial execution over other long-term factors.
> Crafters are more likely to value long-term factors like ease of maintainability, legibility, and accessibility, and may not consider the project finished until those have also been accounted for.
> In my view, the more you optimize for building quickly, the more you optimize for homogeneity.
That’s not possible either with the style attribute.
Now your first instinct might be to "that’s unreadable", but keep in mind HOW you actually read and write this code. You’re not actually reading it to understand what it does like you do with iterative code. You see how the browser renders it, and you just adapt the code. Tailwind code is mostly write-only and maintained by viewing what the component looks like. This code doesn’t need to be reusable either, the whole component needs to be. The Tailwind code inside is unique.
I wrote my first CSS 20 years ago, and one thing I can say for sure is that it's impossible to really structure your CSS in this way. The structure will break down over time and it will anyway be buggy and you'll be chasing your tail, as long as everything you do is global. It might work if you're a solo developer, but the reality is most projects involve multiple people trying to get things done. As long as a style is global, changing it will break something else. These days I use scoped styles and that's it.
My favorite is when colleague A broke something from colleague B, who fixed it but broke sometimes from me, and I fixed that and broke what colleague A did. The process repeated once more and it landed again on my desk, where I said wait a minute, I've been here already. We were than able to fix all three things at the same time.
Can we stop with this kind of trash opinion? Do you genuinely believe people use Tailwind because they don’t know any better? I understand why many people have a visceral reaction against Tailwind when learning about it for the first time, but when you actually use it, you really can understand why some of the "anti-pattern" stuff you learned about CSS doesn’t really apply with Tailwind.
Relying on React or Typscript in LLM era seems very stupid, just have the LLM setup whatever dom manipulation you want and have it write decent JS without slop. Far more offline compatible development almost negligible supply chain issues as well. At least ones you can control.
Layout design issues are orthogonal to choice of language and framework. You can apply the article's approach to plain static pages and to SPAs.
I tend to work closer to the latter end and find that both React and Typescript are extremely helpful to make my code extensible and maintainable. YMMV.
This works great for small sites/apps, but really starts to fall apart if/when it gains complexity where React starts to make sense. I've tried a few times to "just use plain javascript" with the LLM and initial results are often much better, but if the site grows a bit too complex, the LLM starts making a lot of mistakes and it can be hard to reason about as a human and get it on the right track. That hasn't been the case with the React apps IME.
But for mid/large projects, I find that TypeScript brings sanity to JavaScript.
I love some quick and dirty JS project. But after a certain project size I begin the see runtime errors like undefined, NaN, 'false/true' concatedted to URLs, and so on. TypeScript eliminates a ton of those.
What a strange take. LLMs produce plausibly correct output, which is exactly where plain JavaScript and DOM manipulation will result in a spaghetti mess.
Frameworks like React that add structure to the data flow, component encapsulation, and a huge repertoire of patterns to train on, plus Typescript for immediate compile-time feedback loops… those are what LLMs thrive on.
I agree, specially for simple apps. it's much easier to upgrade if you are not relying on 3rd party or NPM's. Don't have to worry about code injections.
I think the (misuse of) so-called "separation of concerns" has been the most harmful thing that happened in web front end development. HTML can CSS are the same kind of concerns: the presentation layer. The idea that HTML is purely semantic and has nothing to do with presentation is just burying the head in the sand.
Separating HTML and CSS into different files is just like separating a bunch of methods/functions into different files, or splitting one monorepo into git submodules. Yeah, it sometimes makes sense, but if you're doing it for the sake of separating things then just stop.
I think the only point of Tailwind is to make front end devs realizing how much separation of concerns is misunderstood and misused as a dogma. Once you realize that you can ditch Tailwind if you like.
Yes, this is the exact harmful idea that I was talking about. If this were true than there would be no 'reader mode' in browser. Or the reader mode would not modify the html at all.
What you see in reader mode is the content layer. HTML is a part of the presentation layer.
> I got curious about what writing more semantic HTML would feel like.
I've been teaching semantic HTML / accessible markup for a long time, and have worked extensively on sites and apps designed for screen readers.
The biggest problem with Tailwind is that it inverts the order that you should be thinking about HTML and CSS.
HTML is marking up the meaning of the document. You should start there. Then style with CSS. If you need extra elements for styling at that point, you might use a div or span (but you should ask yourself if there's something better first).
Tailwind instead pushes the dev into a CSS-first approach. You think about the Tailwind classes you want, and then throw yet-another-div into the DOM just to have an element to hang your classes on.
Tailwind makes you worse as a web developer from a skill standpoint, since part of your skill should be to produce future-proof readable HTML and CSS that it usable by all users and generally matches the HTML and CSS specs. But devs haven't cared about that for years, so it makes sense that Tailwind got so popular. It solved the "I'm building React components" approach to HTML and CSS authoring and codified div soup as a desirable outcome.
Tailwind clearly never cared about any of this. The opening example on Tailwind's website is nothing but divs and spans. It's proven to be a terrible education for new developers, and has contributed to the div soup that LLMs will output unless nudged and begged to do otherwise.
you're unfairly conflating things and putting the blame for a lack of care or understanding on tailwind vs on the dev themselves. nothing about tailwind forces you to build inaccessible or "div soup" apps
can tailwind be used poorly? absolutely. but that's true of any tool
i've been writing CSS for ~20 years and am quite capable with it, having used CSS, Less, SASS/SCSS, Stylus, PostCSS etc. the reason i have settled on Tailwind for the last few years is precisely because it enables me to build more robust application styling.
tailwind frees you from having to spend excessive time building abstractions of styles/classes that will invariably change. placing the styles directly into the markup that is affected by it reduces cognitive load, prevents excessively loose selectors affecting styles unintentionally and really aids in debugging. jumping into codebases with bespoke css frameworks is always more complex and fragile than a tailwind codebase for anything but the most simple sites/apps
add to that the ability to have consistent type, color and sizing scales, reduced bundle sizes, consistency for any developer who knows tailwind and a very robust ecosystem (and thus llms are very familiar with it) and tailwind is a really excellent choice for a lot of teams
tailwind is like most tools; it can be used well or poorly depending on who is using it
> tailwind frees you from having to spend excessive time building abstractions of styles/classes that will invariably change.
Abstractions like a hero image, a menu, a headline? Sure, it's easy to overthink things but most of the time, it's not that complex.
> placing the styles directly into the markup that is affected by it reduces cognitive load, prevents excessively loose selectors
In my opinion, it's the opposite. Besides the obvious violation of DRY and the separation of concerns, inline CSS can't be cached and it creates a lot of noise when using dev tools for debugging. It actually increases cognitive load because you have to account for CSS in two different locations.
Lots of people use Tailwind because they don't want to deal with the cascade, usually because they never learned it properly. Sure, back in the day, the web platform didn't provide much built-in support for addressing side effects of the cascade, but now we have @layer and @scope to control the cascade.
Tailwind uses a remnant of '90s web development (inline CSS) and built an entire framework around it. I get why it appeals to some people: it lets you use CSS while not requiring an understanding of how CSS works, beyond its most basic concepts.
Your framing assumes incompetence across the board which is unlikely to be true for a framework of its popularity. Consider instead competent people are working on projects with different needs and they’ve recognized there are trade offs to both approaches and still decided Tailwind makes sense in their situation.
I disagree with that conclusion. I see tailwind as a cleaner more succinct version of css that is much easier to manage and add features too.
Sure it’s not as dry, but I’ve been bitten in this regard because css framework and templates are so intransparent, preventing me from simply changing padding or margin.
CSS is too detailed and too verbose. Frameworks like bootstrap are too high level and don’t give enough control. Tailwind hits the sweet spot whilst allowing me to be detailed if I want to. It allows me to just get it done.
To be honest, CSS had the cascade but also had horrible tools for actually managing the cascade for a long time.
If CSS had nesting, variables, media queries, the other nice selector queries like :has, and modules out of the gate, we likely would have not needed much of the tooling like tailwind that eventually got built to manage it all with less boilerplate. We built the tools because even when these features rolled out they came in fits and starts so you couldn’t adopt it without polyfills and whatnot.
Premature DRY and premature attempt at separation of concerns have resulted in absolutely horrible spaghetti code in too many code bases.
Many times it's fine to repeat yourself. Many times it's fine for a component to cross multiple concerns.
Can you provide an example of well written components that use Tailwind? Genuinely curious to see what that looks like.
I’ve also been writing CSS professionally for nearly 20 years and am a big fan of tailwind.
The ergonomics in my day to day work are quite nice. To me, the better boundary of abstraction shifted to components, rather than the html/css/js “separation of concerns” that some of the older folks still like to parrot.
However, take a look at the markup and styling for the https://maps.apple.com/ web property.
I can’t deny that it’s quite beautiful and easy to holistically understand. Especially when it comes to the responsive styling—which is when I tend to find tailwind most awkward.
It’s my favorite example of “traditional” CSS structure in recent memory that has given me some pause when it comes to Tailwind.
For what it’s worth I like to encapsulate things in components and still separate out the CSS from the markup.
In my mind it’s the best of both worlds. Vue makes it easy. I think CSS modules in React work similarly
If a tool’s design makes it easy to cut myself, the response is not “people have been cutting themselves for years”.
There is such a thing as the ergonomics of the tool. Yes div soup has been around a long time. But also yes, Tailwind makes the wrong approach the easy one.
It’s ergonomics encourage adding div elements to support styles. It’s the core design loop.
You’re conflating “forces to” and “ergonomically encouraged”.
It’s just the most effective approach, in my opinion. If it’s wrong then I don’t want to be right.
> can tailwind be used poorly? absolutely. but that's true of any tool
Can tailwind be a useful CSS framework? Absolutely, but that can be said of any of them.
Which is precisely why it makes sense to point out it's unique flaws, so that people can make an informed decision as to what works best for them.
If you have some unique feature to tailwind that you think makes it better than the rest, you should share that.
Everything you have listed is also accomplished by all the other CSS frameworks, so it almost sounds like tailwind is simply the main one you have experience with.
> Tailwind instead pushes the dev into a CSS-first approach. You think about the Tailwind classes you want, and then throw yet-another-div into the DOM just to have an element to hang your classes on.
But this isn't a unique flaw for Tailwind. I've been coding with CSS since the late '90s and seen plenty of people throw yet-another-div onto the DOM just to have an element to hang their classes on. Done so myself plenty of times, too.
People have been complaining about div soup for years and years before Tailwind ever came along.
Plus I'm coding with Tailwind now, and almost never think about my classes before my HTML. Nothing about Tailwind in particular encourages you to do so. So I'm quite confused how this is a unique Tailwind flaw.
Even the root comment mentions the exact same failure mode:
> If you need extra elements for styling at that point, you might use a div or span (but you should ask yourself if there's something better first).
This is IMO not worse than vanilla CSS, and it's simply the only way to have customizable layouting in HTML.
that's a really useful way to frame the discussion around tooling
I’ve mentioned this before here, but originally I was against Tailwind because it breaks the Cascading part of CSS, however I think a lot of websites lately work better with “locally scoped” styles as there are just so many different components that many things just dont need to follow a global style sheet. So now I usually reach for tailwind first, unless is a relatively simple vanilla html site
Global styles are like global state in software: They're best avoided.
Global state is bad because it’s mutable. Global styles are not mutable.
Global styling done messily can override local styling. It’s the hardest kind of problem to reason about!
I do think that stuff like bootstrap is generally good at avoiding this but it only takes a handful of improperly scoped high level CSS rules to cause awkward hard-to-fix pain much later on.
No.. that's not the only reason.
Global state is bad because it makes it hard to reason about your system. The global state can affect any part of it, or, focusing on the inverse which is probably better applied to global styles, any part of your system can depend on the global state.
It's also weird to say "global styles are not mutable" - you're right, they're (generally) not mutable, at runtime. But they are mutable in the sense that your developers (you, or your colleagues, or someone in 3 years maintaining your code) can mutate them, and if large parts of your system are implicitly dependent on the CSS cascading properly and so on, then those changes can have unintended consequences.
Of course, that can also apply to tailwind, to some extent. A developer can change a class (custom or otherwise) or the configuration - but at least it is very clear what is being changed and what parts will be affected (just grep).
With CSS unintended consequences are always problems of scoping things better. If I give semantically meaningful CSS classes to my semantic HTML and scope my rules to apply to their intended place in the pages, then unintended consequences don't happen. If I roll like: "Oh, I want this list to look differently, lets make a global scope ul/ol style!" then I am asking for trouble later on. When I write a CSS rule, I should always be thinking about the scope and whether my rules are truly something universally applicable to that scope.
Says the engineer and not the designer
What does the designer say?
So no CSS files at all? Only inline styles?
No, you just need to scope your styles appropriately. See also my other comment: https://news.ycombinator.com/item?id=48161902
Well I mean styles that are actually global I put into CSS, but it’s generally not too much
> nothing about tailwind forces you to build inaccessible or "div soup" apps
https://en.wikipedia.org/wiki/The_purpose_of_a_system_is_wha...
So what if it does not "force" you?
Now do CSS in real life use
Proclaiming professional advice on the internet should compel you to bother using proper caps and punctuation. To your point - you are addressing the symptoms of bad understanding of CSS and it's relationship to the DOM. While tailwinds is a useful tool, it's not particularly special.
> you're unfairly conflating things and putting the blame for a lack of care or understanding on tailwind vs on the dev themselves. nothing about tailwind forces you to build inaccessible or "div soup" apps
+1
> i've been writing CSS for ~20 years and am quite capable with it, having used CSS, Less, SASS/SCSS, Stylus, PostCSS etc. the reason i have settled on Tailwind for the last few years is precisely because it enables me to build more robust application styling.
I think herein lies at least part of the problem of the web these days: Most websites don't need to be applications, and are needlessly made to be applications, often even SPAs instead of simply being mostly informational pages, in turn putting different requirements for styling onto the project.
> [...] jumping into codebases with bespoke css frameworks is always more complex and fragile than a tailwind codebase for anything but the most simple sites/apps
There is no need for frameworks. Well structured and scoped CSS can handle it all.
> add to that the ability to have consistent type, color and sizing scales, reduced bundle sizes, consistency for any developer who knows tailwind
What if not that does CSS already offer? I don't see how normal CSS does not already do that. No additional thingamabob needed.
> very robust ecosystem (and thus llms are very familiar with it) and tailwind is a really excellent choice for a lot of teams
Tons of ready-made stylesheets out there to use for teams. What more of an "ecosystem" do I need to style a web page? Why do I need an ecosystem? Is it not rather a tailwind self-induced need?
> nothing about tailwind forces you to build inaccessible or "div soup" apps
Totally agree. I feel like this was more a by product of React. Not that React forced this either, but it felt like the rise in both went hand in hand for some reason.
While I think it's true that none of the current top FE technologies force the div soup, they don't discourage it either. It would be nice if what ever FE technologies catch on next did try to encourage better practices around things like accessibility. Make the path of least semantic HTML the path of least resistance and allow people to fall into the pit of success, ya know?
Nothing about programming forces anyone to do anything.
That's never been a valid argument to dismiss criticism. It wasn't with Dreamweaver, any it wasn't with visual basic, and it isn't with Tailwind.
Patterns matter. Best practices matter. Path of least resistance matters. Those are all choices you make when you develop a CSS framework. Some of those choices are good and some are bad.
If none of those things mattered, them choosing a CSS framework would not matter at all.
> Nothing about programming forces anyone to do anything
I see you've never written any Go
React encouraged this for years by requiring a single parent element being returned from all components. They also showed a div as the option of choice.
They fixed this later with Fragments but the damage was done.
While I agree I do think there's some "aspiration of purity/correctness" in your approach that I've long let go of.
I look at the royal mess that is HTML/CSS/JS as a necessary evil, required when we want to target browsers. To me it's "just the presentation layer".
In my work I put a lot more emphasis on correctness in the db schema, or business logic in the backend.
When it comes to the messy presentation layer I prefer to write a little as possible, while still ending up with somewhat maintainable code. And for this Tailwind fits the bill really well: LLMs write it very well, new devs understand it quick, and it's quite easy to read-back/adjust the code later.
I 100% agree a Tailwind project is not the best way for a new dev to learn HTML/CSS. But then I prefer the new dev to focus on great db schemas, intuitive APIs, test-able biz logic, etc. Fiddling with the mess that's HTML/CSS is not the place where I consider human attention is best spent on (or where developers pick up skills to become much better developers).
This isn't about "purity/correctness" it's about the real experience of a blind person. Accessibility means caring about the HTML.
Your comment only mentions developers as the audience of HTML authoring, as opposed to users, which is a common attitude and the core problem with Tailwind.
Knowing the DOM structure is properly engineered is the gateway to an entirely compliant end product out the gate. Leaves pros looking for the "difficultly level" on SEMRush after maxing it out.
I use Tailwind and have all kinds of "screen reader" directives in my templates.
Not sure if it helps, but if we get our first blind user I will gladly make some admends to make it more usable for them.
It seems that Tailwind is now blamed for the mess that is HTML/CSS. Tailwind certainly allows for accessible designs; it may not be the ideal solution, sure, but what we aim for is "good enough".
> but if we get our first blind user I will gladly make some admends to make it more usable for them.
Isn't this slightly backwards? Why would blind users sign up if the platform isn't usable for them in the first place? It has to be usable for them for them to become users :)
> Not sure if it helps, but if we get our first blind user I will gladly make some admends to make it more usable for them.
I have heard "we don't have blind customers" argument many times before. Apart from ethical issues that this raises, ADA requirements, technically, don't care if you have blind users or not. Accessibility is still required...
It's a great way to make an easy $10-30k as a law firm too when you sue them. Especially when you have definitive statements like "we purposely don't care about a11y until we get sued for it."
> Not sure if it helps, but if we get our first blind user I will gladly make some admends to make it more usable for them.
How will you know if they are unable to use your site? They'll just leave.
It's not just blind people, but also people with reduced eyesight. As I'm getting older, I really appreciate good contrast and the possibility to zoom in without breaking the layout.
People always forget they're one unlucky event from losing their sight, hearing, dexterity, or more. Hell, like you say, even aging's enough to do it. Accessibility suddenly becomes a lot more important when you're on the other side of the "not a priority" talk.
And how does tailwind or the structure of the underlying html of the page change or affect that?
If Tailwind lends itself to using pixels instead of relative units for things that should be relative (like font-size, line-height, etc.), that's a problem. For those users, the HTML elements matter less unless they're savvy users who have custom user stylesheets to selectively adjust the appearance of content instead of changing everything on the page by zooming (e.g. make links, buttons, paragraphs, list items bigger and/or a different font or weight).
if we get our first blind user I will gladly make some admends to make it more usable for them.
Not good enough. You have to be accessible before it is needed in order to avoid legal liability.
And how do you expect to get a blind user if they already cannot use your product?
None of the doctors I build web sites for are currently blind. I know this because I talk to them regularly. But I still build the web sites for the future, when HR might hire a doctor or nurse or other person who is blind, or partially sighted, or has trouble with their muscles, or has difficulty distinguishing colors.
Doing the right thing isn't that hard. Not doing it is just lazy.
You call it lazy. I call it "focus" or avoiding pre-mature optimization.
I find the "legal liability" claim hilarious... I do better than 95% of the web: as I said I HAVE some screen reader directives (just did not test it), and labels to make the app more accessible.
> You call it lazy. I call it "focus"
Is this to be read that disabled people and their needs, or more directly from the replied-to comment, "doing the right thing", are not a focus of yours, flossly?
A former coworker of mine opened a meeting saying "we are so good, we care about accessibility". I had been complaining for months and finally a customer had said "we won't buy your product unless it complies to the law".
I find the "legal liability" claim hilarious
You must have six million dollars laying around. Because that's the penalty Target paid for not having an accessible web site.
In 2008..
That wasn't even a regulatory penalty, but a class action by the National Federation of the blind.
https://en.wikipedia.org/wiki/National_Federation_of_the_Bli...
We use tailwind and are capable of building accessible websites without any issue. People could make all the same mistakes with CSS for accessibility. It’s the not knowing how to make accessible content that leads to inaccessible content, not the tool you use to implement the styling.
Sounds like you're kind of just talking your book though. Person who makes accessible sites suggests you need an accessible site. Blind people aren't the only ones who might need modifications. You could have an infinitely long list of adjustments for all kinds of disabilities, and tell me I'm lazy for not doing each of them. Why are blind people special?
You are lazy for not doing accessibility adjustments, because accessibility isn't for blind users. It's for the deaf ones, the ones with poor eyesight, the ones with mental deficiencies, the ones with motor issues like Parkinson's, the ones browsing your site shitfaced at 4AM, and so on and so on.
Accessibility isn't a checklist to cover your ass for a percentage of the population: it's for everyone. It literally makes your website less shit. You slapping an aria-label doesn't fix things.
Every moment you spend doing accessibility is a moment you spend not doing other things. You could argue it has a high RoI to do accessibility, fine, but that doesn't make it lazy _not_ to do it. Maybe I have even higher RoI/EV stuff to be doing.
> Maybe I have even higher RoI/EV stuff to be doing.
I mean, to readers of these comments, I think it's right there for you: 0x3f will take "higher ROI" over "accommodate and support disabled people".
Yeah, thats explicitly what I'm saying so I'm not sure it needs repeating. That has very little to do with it being lazy though, is the point.
We were already implicitly discussing RoI when we were talking about 'legal consequences' above. This is how people decide between alternatives, generally.
You just told a bunch of potential and current customers that they're not worth the ROI.
Pretty sure they'll remember that, and they'll talk about it a lot.
Picking subsets of customers to focus on is a totally standard part of running a startup or company in general, so this is not really news or any kind of threat.
You might as well tell me the suburban moms are not going to buy my developer tool because I've personally slighted them with the branding. Why would I care? I made my decisions knowing this.
In fact ditching low RoI customers is incredibly common and good startup advice.
This is just admitting that your product is small and unimportant.
Hardly, I could easily find Fortune 500 websites without accessibility.
I suspect as the years change and you continue to get older you will likely revisit this idea mentally.
But you do you, boo
Accessibility is done while you do it. Not as an afterthought.
But if you're having a higher ROI writing absolute crap, feel free, it's not my website.
You're just expressing a normative view here, it's not very interesting or informationally-dense. You care about accessibility more than I do. That doesn't make not doing it 'crap'.
I mentioned blind but there’s lots of others. Folks sitting a desk whose eyesight are getting worse and are scared to say so for fear of losing their job, for example. This happens.
Side note: if you aren’t deliberately choosing semantic elements and instead dropping aria attributes onto a bunch of divs this is an anti-pattern.
What does Tailwind have to do with accessibility? Most significant HTML markup is block level elements. The CSS is completely orthogonal.
I feel like old-school frontend devs bring up accessibility as a kind of bogeyman.
It reminds me of the myth that CSS style X or Y breaks accessibility "because screen readers expect semantic CSS classes". Zeldman (of A List Apart) promulgated that disinformation for years, until someone actually told him screen readers don't work that way. 90% of people who use a11y as a rhetorical cudgel have never actually used AT themselves.
I'm not familiar with that old tale about Zeldman. It's true that assistive technologies don't know about CSS class names but CSS absolutely can affect a non-sighted screen reader user's experience.
I don't use Tailwind so I don't know if it makes it easier or harder to do the right thing when needing to hide something from everyone or only visually hiding something. Because it's CSS, it can't take care of only hiding something from assistive technologies.
It’s not Tailwind the tech, it’s the ergonomics of the tool. Tailwind’s design loop encourages “let me add a div so I have a place for my CSS class”.
I’ve usability tested and performed user research with many users needing assistive tools and I’ve used them myself as part of design.
Basic HTML authoring is good practice for many reasons.
> let me add a div so I have a place for my CSS class
As opposed to what exactly? HTML doesn't let you lay out stuff properly without at least some structural divs that have no meaning.
If we have the proper aria properties for example, why does it really matter if I have extra divs (which is, again, irrespective of tailwind)
But why would I spend any time mastering this skill when we have AI now?
Disability software that uses both the markup and the on-screen visual for decision making is likely imminent and would render most of this no longer necessary.
Claude Cowork is already doing navigation and web browsing by screenshot showing this is possible.
I guarantee you no one is working on this.
You don’t think it’s valuable taking time to improve the interaction layer that all your users interact with your app through?
A few counterpoints:
Treating markup and styles separately is great, in principle, but you'll always need additional markup for certain things. We knew this going back to the early 2000s.
There is nothing about Tailwind itself that forces you to use divs and spans instead of the appropriate HTML tag.
Documents and interfaces are different. Tailwind makes a lot more sense for interfaces. You can use Tailwind for the interface and scoped HTML selectors for other content.
Tailwind is around 4x faster and has practically no overhead compared to writing a complex CSS codebase. Whatever you think of it, this is always a benefit in its corner.
Folks in this thread keep conflating “forces to” and “ergonomically encourages”.
If a power tool is poorly designed it may not force me to hurt myself but if it makes it easier that’s a problem.
I always feel like the distinction between interference and document is missing in these types of discussions. Often times they're as different as native vs web dev and if you don't realize that then you're arguing about totally different things and nothing will make sense.
Benchmarks?
As someone who wrote CSS for 20 years and who was against using Tailwind because of “principles” I must say that Tailwind is just awesome. Every minute spent trying to make sense of the structure past you or your colleagues came up with is a minute that could be spent on something more important.
Every time someone says that Tailwind sucks, it’s like hearing the old me speak.
Same here. It’s super weird take to me now. Maybe if you’re just writing plain HTML and CSS tailwind would be worse, but assuming there’s a component system you’re going to be just fine. The cascade of CSS is such a foot gun. Localized styles work great and tailwind abstracts away hardcoded values with relative ones
I prefer writing plain CSS over Tailwind
But I get component-scoped CSS (via Vue) and use custom props to abstract away hardcoded values
Tailwind isn’t the only option for those features
It's unfortunate Inverted Triangle CSS (ITCSS) isn't more popular. Instead of resisting the cascade, it embraces it and makes it work for the developer.
The summary: write your CSS in specificity order [1]:
ITCSS basically does away with specificity wars in a CSS codebase. Usually the only place !important is the utility layer.[1]: https://matthiasott.com/notes/how-i-structure-my-css
Aren't Cascade Layers [1] a more reliable, native solution to the specificity problem? In 2026, why not lean on them instead of source order?
[1] https://developer.mozilla.org/en-US/docs/Learn_web_developme...
This is brilliant, I was not aware of ITCSS. Thank you for sharing! The link you shared fits my brain a lot better than pure BEM/CUBE, which works but always felt weird and uncertain to my style. Sprinkling a bit of BEM on top of ITCSS feels just right. shame.scss is the snarky cherry on top. Thanks again, you have enlightened at least on person today! :)
Using tailwind doesn't lead to any inherent concession of accessibility. How do you come to that conclusion?
If I look at their component library, they also do the work of including aria attributes for you https://tailwindcss.com/plus/ui-blocks/marketing/sections/pr... (first exsmple with free code I've found).
If we're not talking landing pages, which are more like digital brochures, I always start with markup and then add css classes on top.
> If I look at their component library, they also do the work of including aria attributes for you
Using ARIA attributes instead of semantic elements is bad for accessibility.
How are ARIA roles/attributes bad for accessibility?
Sure, if there is a HTML element that works then use it, but not every UX pattern is expressible in HTML without specifying roles/attributes (e.g. tabs [1]) and not all browsers support recent HTML elements/attributes (such as using details/summary for accordions).
ARIA patterns [2] has a list of examples for UX components and their examples specify/use ARIA roles/attributes.
[1] https://www.w3.org/WAI/ARIA/apg/patterns/tabs/
[2] https://www.w3.org/WAI/ARIA/apg/patterns/
WAI's APG patterns exist to document how ARIA attributes should work, they don't advocate for them to be used in place HTML elements. They also don't test to confirm that they actually work in browsers or with assistive technologies (some specific patterns are fine). For web developers, they're helpful for documenting expected keyboard interaction support and other norms.
Are you still coding to support Internet Explorer? All browsers have supported details/summary since an Edge switched to Chromium in 2020.
https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/...
Who said inherent. The design loop of “I need a div for my CSS class” is an ergonomic problem not a concession.
I agree. I don't really like Tailwind, nor similar CSS frameworks. The whole idea was to separate styling from HTML, and Tailwind is putting it back into HTML through the backdoor. It's just a way to do styling from your HTML without having to touch the CSS, by inserting styling info in your HTML. That's exactly what we were trying to move away from.
I think this assertion is where most of the conflict comes from.
There is a fair amount of people that disagree with the premise that it should be separated in that way (Including me).
I personally like this essay by the author of htmx on the topic
https://htmx.org/essays/locality-of-behaviour/
Also just better composition imo.
Practically I think this means components of scoped css, html, js.
People never seem to have the same complaint about mixing or separating app code and sql in the same way?
I separate those too. Queries get their own file. Sometimes their own framework.
What's a good source to learn how to develop like this - to create HTML / CSS structure that's accessible?
EDIT: ignore. I can see you have some links in your profile. Will check it out.
I agree with the criticism of tailwind. IMO any good criticism warrants at least an opinion on what should be done instead or some corrective or remedial patterns.
there is a reason why tailwind got as popular as it is today. And it only highlights the gaps in either what HTML and CSS provide for the task at hand or the difficulty in that approach. This must not be lost in any criticism.
another observation is none of technical user interface decisions or discussion emphasis on the tree data structure that is inherent to every major user interface rendering mechanism relevant today. there are inherent benefits and drawbacks of it being a tree structure that neither of the developers nor the framework leverage. when thought of as a tree, it benefits from adding certain constraints and naming conventions that allow more artistic expression using just HTML and CSS that I have not seen tailwind or any other framework encourage
> HTML is marking up the meaning of the document. You should start there. Then style with CSS. If you need extra elements for styling at that point, you might use a div or span.
IMO this is the fundamental problem with HTML and CSS. You'll always have some part of the styling in the HTML due to needing extra divs and spans. At that point splitting the styling outside into the CSS splits your attention and Tailwind "solves" that by moving everything back into HTML.
Note that I don't like Tailwind, but I would rather have a way of styling that does not need to rely on the existance of extra divs and spans to work.
You're not wrong, and I mostly agree with you. I die inside when I see the div soup that a lot of sites have become. However, I think there is value in being able to have the important parts of CSS merged into the HTML a bit. Where that line is, is certainly up for debate (and I don't have the answer), but I've found a lot of my tailwind sites are more readable to me than my pre-tailwind sites, often because I don't have to context-switch and open a different file to be able to reason about the styling on an element. For big stuff the second file can be nice, but there's a lot of style tweaking that is great to be able to do right there in the HTML. Tailwind does really lead you to ignore the css file though (or keep it highly minimal), which I agree is becoming an anti-pattern.
The "open a different file" reasoning piece is a common pro-Tailwind statement and I do see the upsides.
I think that upside became more prevalent in the reusable components era, whereas previously CSS was targeting an entire HTML file (and thus the reasoning was more like SQL query than "this one element's styling").
With LLMs I think this upside is much smaller now though.
> With LLMs I think this upside is much smaller now though.
With LLMs Tailwind wins. Because it's a very restricted set of classes. With regular "separation of concerns" CSS, LLMs will happily just pile on more and more and more CSS because they can't really analyze the code that's already there, and will miss and re-create huge chunks of CSS. Or write increasingly hyper-specific CSS to fix reported issues.
Anecdotally: in a side project I now have 10k lines of "pure" CSS generated by LLMs on top of Tailwind. The web part of the app is ~20k lines (not all of them are rendering anything on screen). No idea how to fix it :)
It seems that everyone is forgetting the web inspector as a tool for designing web pages. You can tweak properties and styles in a live environment, and then transfer your preferences to the css files.
I don't have to context-switch and open a different file to be able to reason about the styling on an element
Unless you're coding on a VT100 terminal, you just put the HTML in one window and the CSS in another. Subdivide as necessary, or as your monitor space allows.
Heck, we were doing that back in 1989 on IBM PCs with MDA displays.
If your CSS is so out of control that you can't wrap your brain around it, it's time to refactor or split into individual CSS component files.
VT100 for life!
But more seriously, I should have been more specific. Having the second file open in a split pain isn't that big of a deal, but having to navigate and find the right selectors can be. If class names are used well then it's pretty easy to find those, but my experience with that is riddled with inconsistency when I'm not the one who wrote it.
On that note, it's also much easier to review CSS changes in pull requests when they are right in line with the file. Otherwise I have to do the same lookup to find the corresponding HTML, and reason about whether the selector could potentially be grabbing things that aren't obvious, etc.
Even if one were limited to a single window, I’d hazard to guess that most modern web frameworks and languages have some degree of templating and pre-processing capabilities, which let you have logically contained files with HTML and CSS that decompose/render into separate files.
I’ve made setups like that on a number of projects (ASP.Net & various .Net web frameworks). keeping clean separation of concerns, proper cascading, but also a simplified development experience where the ‘component’ file contained all relevant code, markup, and local styling.
Maybe even split it into a set of small reusable coherent utility classes
I find your comment quite refreshing.
25 years ago, I was appalled how Microsoft Frontpage could transform a very simple word document (with little formatting) into an utterly indecipherable mess of HTML that rendered correctly.
With very simple transformations, I could paste the text of the document into notepad and add just a few heading tags for the same rendered result but a much more understandable source.
CSS had a lot of promise for simplifying the HTML content, but the world tried its hardest to prevent that.
Now we have multi-megabyte monsters for simple webpages (before even counting graphics).
HTML is marking up the meaning of the document. You should start there. Then style with CSS.
This is precisely how I do it.
Code that generates HTML. Once I can see all the content on the screen in some kind of Netscape Navigator 1.0 nightmare, then I go back and add styles to make it look pretty.
It's not hard. It just requires thought and planning.
(The best planning tool I've found is a pencil and grid paper, not the web design SaaS-of-the-moment. However, it's surprisingly hard to find good pencil sharpeners these days.)
Which semantic element(s) would you use to build the example from the Tailwind website?
> Tailwind instead pushes the dev into a CSS-first approach. You think about the Tailwind classes you want, and then throw yet-another-div into the DOM just to have an element to hang your classes on.
To be fair plopping a `div` everywhere started way before Tailwind. I blame React and the mess that is CSS in JS for this.
Divitis was a thing long before React came along. It was a common solution to styling problems even in the jQuery/Dojo days. Getting stuff to look similar across IE6 and FF before CSS3 relied heavily on divs.
It did for sure. And Tailwind absolutely doesn’t need to be done this way. I think this is a correlation-not-causation issue
I disagree. With Tailwind you think in nested classes which ergonomically encourages “I need a div for this class”.
Very similar to early React where every component had to return a single real parent element (now you can return a fragment) so people chose div.
> Tailwind instead pushes the dev into a CSS-first approach. You think about the Tailwind classes you want, and then throw yet-another-div into the DOM just to have an element to hang your classes on.
I wholeheartedly disagree. That mindset is not caused by Tailwind, but by being ignorant.
You can perfectly create an HTML document with semantic meaning and the add Tailwind just as any other CSS framework or pure CSS to it.
And DIVs do not carry meaning, they are specifically to add functionality or styling, so you can throw in as many as you like. Using them abundantly isn't good style, but the way you make it sound that they're evil isn't good either.
The HTML spec says divs are the element of last resort. This issue isn’t that they’re bad. The issue is they are reached for far too quickly.
Also if you think massive numbers of nested divs don’t have a performance impact in the DOM when reusable components are nested (because “styling”), you’re wrong.
> The HTML spec says divs are the element of last resort. This issue isn’t that they’re bad. The issue is they are reached for far too quickly.
The problem is that HTML gives us very few tools to do anything useful. And you can only push certain elements so far. Div and span are generic elements with no semantics attached. You want a layout? Div. You want a change to a part of text? Span.
The only reason they are called "elements of last reserve" because it's only true if you remember that HTML is, has been, and forever will be a tool to display static text, badly. That's why you have article, section, p, and other text-oriented elements. But the moment you want something beyond that? Welcome to divs.
> HTML is marking up the meaning of the document. You should start there. Then style with CSS. If you need extra elements for styling at that point, you might use a div or span (but you should ask yourself if there's something better first).
> Tailwind instead pushes the dev into a CSS-first approach.
You're putting the cart before the horse. Or forgetting either the cart or the horse. Tailwind doesn't force anything. And "semantic HTML" or "semantic CSS" are not really a thing, and have as much bearing on how many divs a page has, as Tailwind.
And the reason is simple: there's literally nothing else in HTML than divs and spans. The amount of usable primitives is absolutely laughable, and trying to combine them in any useful manner results in as much soup with Tailwind as without Tailwind.
> since part of your skill should be to produce future-proof readable HTML and CSS that it usable by all users and generally matches the HTML and CSS specs.
Which part of Tailwind isn't readable, isn't future-proof, or doesn't match HTML and CSS specs?
How is "px-4" none of that, but ".ytp-big-mode.ytp-cards-teaser-dismissible .ytp-cards-teaser-label" (Youtube's CSS) or ".swg-button-v2-light[disabled]" (Washington Post) or "legacy-popover--arrow-end-bottom:after" (Spotify) are?
> The opening example on Tailwind's website is nothing but divs and spans.
Oh no! And what are the opening examples on any of the "proper pure-as-god-intended CSS" sites?
> Oh no! And what are the opening examples on any of the "proper pure-as-god-intended CSS" sites?
The first example on https://developer.mozilla.org/en-US/docs/Learn_web_developme...:
No divs and spans in sight.True! And Mozilla is one of the good guys.
What I should've said in my hastily written comment should have been: "and other implementations of the same (or other) functionality isn't divs and spans?"
I think my only true criticisms for Tailwind example would be:
- should've probably used h2/h3 for card titles. Though this is dependent on where and how the card is used
- should've done more with the meta (number / date). But in a real world these would probably still be spans (for example, to mark them in different colors etc.)
HTML doesn't have a card element. So when you create one, you... use whatever's available. And divs and spans in HTML+CSS literally exist to manipulate layout and text.
BTW, my favorite accessible card is this one: https://inclusive-components.design/cards/ And it's probably even more weird. Demo: https://heydon.github.io/Inclusive-Components/cards-redundan... (check the CSS also)
> HTML doesn't have a card element.
I feel like this is a bad example because “card” is a presentation thing, not a content thing. On a social media site, you can have cards with submissions, in which case <article> is the proper tag – and “card” is just a way to style the submission, so it deserves to be a class.
> I feel like this is a bad example because “card” is a presentation thing, not a content thing.
It is both, and herein lies the problem with HTML and the quest for purity. The content you display in a card differs from the content you display in a different context.
The world is filled with "bad examples".
> so it deserves to be a class.
I guess I haven't looked at <article> docs since it was introduced many years ago [1]. Talk about "semantic" lol. The entire definition has been twisted and turned to be nearly indistinguishable from a <div> element. TIL that "product card" is an "article" [2].
I guess the reason why people use divs is that they may look for a corresponding semantic element, but don't see it in the list, and don't look into technical details, so reach for a generic div.
Interestingly enough, best practice is (or was a couple of years ago) to actually use a card as a list element in a list, see: https://wpaccessibility.day/2024/sessions/how-to-design-and-...
[1] Originally, of course, they were always meant for texts that "could be published or syndicated separately if needed" https://www.w3.org/WAI/GL/wiki/Using_HTML5_article_element
[2] https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/... while the spec still talks about mostly actual articles and text content: https://html.spec.whatwg.org/multipage/sections.html#the-art... HTML as text-only and text-centric markup is uniquely unsuited for... well, almost anything (even for most text use cases).
CSS is badly designed and uses a confusing, separate DSL with arbitrary rules designed before the Internet was widely used, before web apps existed, before smartphones etc
It's trash and throwing it out is good. Not learning it is good. Tailwind is a solution to a real problem.
More importantly, AI is good at it already and it's unlikely humans will need to understand HTML/CSS at all within a year or two. There's no reason to spend time learning how the gears work, just put the cover back on
“CSS is bad” Why? “Because reasons.” Care to explain? “No need to learn it anymore, my AI can do it for me” Okay, but why is it bad to learn it “Reasons”
Uh… what?
>It's trash and throwing it out is good. Not learning it is good. Tailwind is a solution to a real problem.
Yup. Spent a decade of my career writing CSS every day, I was what you would call a "guru" and have written easily hundreds of thousands of lines of it over the years. Haven't touched a class or a stylesheet in nearly a year now, and probably never will again. Good riddance.
Same for me, I started web development with Netscape and IE5, and all the browser specific CSS declarations and media queries, checking on so many different browsers. Then all the trouble with positioning by inline blocks/float, then flex boxes. Now AI does all the HTML structure and CSS. Never have to do it again.
I really, really love Julia Evans writing.
She writes from a place of vulnerability and honesty. Most people write to sound smart and she writes to say "I don't know it all but there are some things I discovered I want to share." I almost feel like she writes to share things with people she loves, even though she doesn't know them directly.
She spoke alongside Randall Munroe at the last Strange Loop (RIP). Some people waited to talk to him afterwards, but I waited to talk to her. I don't think she got my joke that she should rewrite her bash scripts into perl and for that I'm truly sorry.
> I almost feel like she writes to share things with people she loves, even though she doesn't know them directly.
Thank you for articulating this!
I'm not Julia, but I'd just like to put down here that this is pretty much my philosophy for public speaking/giving presentations, and I have been trying to instill it in some coworkers who struggle with presentations. It's a great privilege to be able to convey to one's peers and loved ones things that you're (likely) a bit more familiar with than they are and which may help them with some matter.
CSS Modules are a simpler solution to cascading problems. They create unique class names, so your classes don't clash [1]. And they don't have the two main downsides of TW, which are readability [2] and tooling. Tooling for debugging and experimenting interactively with Chrome and FireFox DevTools.
[1] https://x.com/efortis/status/1888304658080256099
[2] https://github.com/ericfortis/tailwind-eye
One thing that has always struck me about Tailwind is that practically every argument its proponents use more or less boils down to “I never learnt CSS beyond a junior level”. It’s super common to hear Tailwind advocates say things like “Without Tailwind, we would just have one big disorganised CSS file that always grows uncontrollably and ends up with loads of obsolete stuff in it and !important everywhere! Tailwind is so much better!”.
CSS is a skill just like any other technical skill. If all you do is learn the bare minimum so you can bodge things until you get something that looks right, then your ambitions are going to outpace your ability to keep things organised very quickly.
It's worse than that; the common arguments for Tailwind literally derive from total ignorance of how CSS is made to work, and a disposal of guidelines that developers would worship in any other context (i.e. Don't Repeat Yourself).
It's really frustrating to be talking with someone about Tailwind and CSS, and realize that not only do they not know what "cascading" means, they never even considered the concept might be useful in the context of a stylesheet.
Tailwind, JS-in-CSS, and the like have become popular because they work well with the modern corporate UX workflow. A Figma component has a certain set of styles, you apply those same styles to the corresponding React component.
And none of this really violates DRY, your unit of reuse has shifted from a CSS class to a framework component. There's nothing precluding you from using an approach like DaisyUI if stock Tailwind has too much repetition for your taste.
> A Figma component has a certain set of styles, you apply those same styles to the corresponding React component.
This is what CSS classes were made for. Of all of the arguments in favor of Tailwind, this is the one that drives me battiest. Say what you will about CSS, but "give a name to a re-usable set of styles for a component" is pretty much as fundamental as you can get.
> And none of this really violates DRY, your unit of reuse has shifted from a CSS class to a framework component.
Sure, sure. except for the inline styles everywhere. And the fact that everything is literally being repeated all over the place. But other than that, no repetition!
> There's nothing precluding you from using an approach like DaisyUI if stock Tailwind has too much repetition for your taste.
...and now you have three problems.
> This is what CSS classes were made for.
That brings with it the problem of naming a thousand things in a consistent way that everyone on your team needs to understand and remember, otherwise you end up with tons of duplicated classes, parallel systems, and bike shedding. Have we, as an industry, not felt this pain often enough yet? Do we really need to keep banging our head against the wall to figure out it does hurt?
> Sure, sure. except for the inline styles everywhere.
There are no inline styles when using Tailwind. There are references to variables from the design system.
> And the fact that everything is literally being repeated all over the place.
If you find yourself repeating the same sequence of classes, it's time to create a component in your frontend framework if you use one, or a Tailwind utility class. And even if you just copy-paste the same class strings all over the codebase, transport compression will eliminate that pretty much entirely.
> That brings with it the problem of naming a thousand things in a consistent way that everyone on your team needs to understand and remember, otherwise you end up with tons of duplicated classes, parallel systems, and bike shedding. Have we, as an industry, not felt this pain often enough yet? Do we really need to keep banging our head against the wall to figure out it does hurt?
Of course. It's obviously better to have 10,000 different names that are all loosely, but not exactly the same as the CSS property they're trying to represent.
I for one do not understand what is so difficult about making a team internal decision about how some "component" (here in quotes, as I am actually thinking of an HTML subtree with specific purpose somewhere on an HTML page) is going to be named, and then give it that name as CSS class. Are people never talking with each other? Are people unable to grep a code base, before making up a new name? And how many similar but not same purpose things do you have on your pages, that this becomes a serious problem? Or is it just a discipline problem? People can name hundreds of useless OOP abstraction classes, but cannot be bothered to think of a good name for a "component" on a web page?
I mean, come on, there is usually tons of context and team internal language for the new thing to build and to talk about it, distinguishing it from the old thing that was already built.
And if that's too hard, then allow the design department to name the things they design and notify them about any clashes. They must have a design language anyway.
> And even if you just copy-paste the same class strings all over the codebase, transport compression will eliminate that pretty much entirely.
The client still has to decompress it and waste processing power parsing all the repeated text.
Premature optimisation doesn’t even fully express how absolutely ridiculously futile it is to try and make your website faster by having fewer CSS selectors.
It’s like my grandparents worrying about immediately switching off their LED ceiling lamps when they leave a room - meant well, but utterly meaningless.
If this was the case, it wouldn’t take several seconds to open devtools on sites that use Tailwind.
That is only the case for sites that include the entire, unpruned Tailwind Stylesheet with all utility classes. That’s a choice…
There are a bunch of differences between Figma styles and CSS styles that prevent you from creating a 1:1 mapping: typography inheritance, spacing rules, and variant specificity to name a few.
Like yes, CSS by itself is extremely powerful, but I see no reason why you should feel beholden to use all of its features simply because they're there.
> Sure, sure. except for the inline styles everywhere. And the fact that everything is literally being repeated all over the place. But other than that, no repetition!
Well, instead of repeating inline class names everywhere, you end up with CSS properties repeated everywhere. Not really seeing the difference.
> Well, instead of repeating inline class names everywhere, you end up with CSS properties repeated everywhere. Not really seeing the difference.
Erm...what now? That's so off-the-wall that I can't even wrap my head around your meaning.
Are you trying to argue that because, say, a conventional CSS file has "border:1px" in multiple places, this is somehow equivalent to the Tailwind approach of making a "b1p" class that captures the same thing [1], and plastering it across your templates?
Because a non-abusive application of CSS would actually just put that border property in a semantic class like ".widget" or something, and sure, you'd have multiple "border:1px" declarations across all of your CSS files, but that's irrelevant, because you're not trying to reconstitute every style inline from pseudo-properties.
[1] I am making this example up for illustrative purposes.
Yes, that's exactly what I'm saying. You don't end up needing a semantic class like .widget since you likely already have a Widget component in your codebase. Essentially:
vs You keep saying this is an abuse of CSS and that's not how it was meant to be used, but why is that so important?> Well, instead of repeating inline class names everywhere, you end up with CSS properties repeated everywhere. Not really seeing the difference.
It’s like the difference between
and Any good programmer knows why the former is better.> but "give a name to a re-usable set of styles for a component" is pretty much as fundamental as you can get.
Yes. And as 30 years of CSS show, it's not enough.
> Sure, sure. except for the inline styles everywhere. And the fact that everything is literally being repeated all over the place.
It's not repeated all over the place, because in your codebase you have a single place where component A is defined. A single place where component B is defined etc.
I don't see you complaining about having to repeat the same CSS properties (padding, margin, display etc. + responsive styles + hover/disabled etc.) for half of the components when writing vanilla classes.
The common arguments against Tailwind usually derive from total ignorance of working with CSS on large scale projects with many team members.
And when this is pointed out you’ll usually get replies that just hand wave it away as not a problem, as if things like BEM were invented for no reason.
Yeah, that's a straw-man. I've worked on large-scale projects with many team members, and it's perfectly possible to use CSS as it was designed.
But sure, like most tools, it starts with understanding how it works.
Sure it's possible, but is it possible for everyone on your team? Including the new hire, the interns, or the now-vibecoding managers?
Sooner or later it deteriorates.
Sure, but "total ignorance of how CSS is made to work" is also a straw man.
“That’s a straw man”, he says, as he hand waves it away as if it were not a problem
And the cascading thing is a nightmare even after years.
Whenever i have written CSS/TailwindCSS which was unproblematic to extend it was when i literally switch thinking to use least amount of properties and let the page flow.
Whenever i see tons of css i know it’s brittle and will cause hours of wasted time down the line to fix something which already should have been fixed.
The more experienced Tailwind proponents probably have better things to do than get dragged into yet another online flamewar :) I've done tons of CSS since the 90s before looking into Tailwind. After it clicked, I've mostly tried to avoid raw CSS. In a sense, you exchange one mess for another. Personally, I'd rather deal with a localized class soup than trying to make sense of overlapping, often contradictory, cascades of styles across multiple files. Both can be implemented cleanly, but I'd much rather clean up a Tailwind mess than a CSS one. And I find the development process much more enjoyable overall.
> Personally, I'd rather deal with a localized class soup than trying to make sense of overlapping, often contradictory, cascades of styles across multiple files.
That seems like a false dichotomy. I'm a huge fan of locality (both in software engineering and in physis) but you can also "localize" your styles by scoping them appropriately. (Modern frontend frameworks typically do that automatically for you at the component level.) There is no need to use Tailwind for that.
> both in software engineering and in physis
I meant physics of course!
Yes, you could build your own framework to localize your styles. Or you could just use Tailwind.
You're misunderstanding me. I never said you should build a framework. I said frontend frameworks already provide style locality out of the box, so there is no need to introduce an additional framework (Tailwind) for that.
You aren't wrong, but the _overwhelming_ majority of "full stack" devs I've worked with only know CSS at the most basic level and have little interest in learning it in depth. I myself have been programming for more than 20 years, doing web dev for almost 15, and I can't find the motivation to learn it well. There are too many technical skills to keep up with and CSS is pretty low down on my priority list. I would prefer to rely on specialists who are experts but companies aren't willing to hire dedicated front end devs.
Isn't Tailwind easy to understand when you look at the codebase, rather than putting in more effort to learn a pure CSS codebase? Isn't that part of the argument of Tailwind being easier to scale?
> Isn't Tailwind easy to understand when you look at the codebase, rather than putting in more effort to learn a pure CSS codebase?
No, I don’t think that’s the case at all.
Isn't that part of the argument of Tailwind being easier to scale?
I think that was true at the beginning. But Tailwind is quickly approaching the multi-headed hydra it was trying to replace.
What exactly has changed about Tailwind in, like, years? There are a few more properties for new CSS features, a few convenience features (like size-x instead of w-x h-x for the same values of x), but other than that? If you've grown accustomed to the utility classes eight years ago, then disappeared under a rock until today, you should be able to continue working in an unrelated, Tailwind-using project immediately.
Isn't the same true of those who use a library that wraps SQL?
I think there is a wide spectrum of "wrapping SQL", and very likely not because someone doesn't know SQL well. Depending on the use case, it can be just the right solution or overkill.
Tailwind, on the other hand, attempts to address a different set of problems, but I am not getting into that here -- other comments have summarized it well.
I would think manipulating CSS through user input is both less common as an application pattern and less critical as an attack surface.
Your comment is getting downvotes, not necessarily because you are wrong, but 1) CSS is indeed hard, complex and often confusing, which is partly why Tailwind exists in the first place 2) your comment points out some inconvenient facts, and people don't like that
That’s the value of tailwind. You can just skip learning CSS and still get a good result. The benefit to learning it is marginal.
> You can just skip learning CSS
It works until doesn't, and you'll have to figure out what's going on with your code.
Personally, I'm not sure from my own dives into it that I'd still insist on bare CSS in a professional codebase any more than I'd insist on plain DOM manipulation. And I do at least see Tailwind classes as being a little less of a DSL than other, similar tools. But while I'm not going to be a purist about it at a workplace, I both agree with you and have noticed a layer even beyond your point: that overreliance on these things leads to not learning HTML beyond a junior level.
It gets really easy to lean on class-based CSS and use a `<div>` for everything instead of ever learning what a semantic element is.
And that contributes to other bad habits, like writing a bunch of JavaScript to define behavior that could just be natively handled by your browser.
A weird personal irony is that because no employer has ever asked me to directly write CSS, what's actually made me better at CSS is JavaScript -- namely that my understanding of selector logic has improved a lot after picking up Web scraping.
I have been writing a "clean" web development guide focusing on writing HTML and CSS that scales well: https://webdev.bryanhogan.com/
Maybe it's useful for people here. I don't use Tailwind or similar for styling, just CSS with modern frameworks like Astro or Svelte.
For every project I have the following CSS files:
- reset.css
- var.css
- global.css
- util.css
Other styling is scoped to that specific component or layout.
Using a JavaScript framework kind of defeats the whole purpose doesn’t it?
css modules with react are really nice to work with. Plain css scoped to a component. The two arnt always at odds
Sounds like a home made Tailwind of your own
Nice article!
I'm a fan of removing any dependencies on external libraries and writing my own solution from scratch, but there's a good reason why I decided not to do so with Tailwind: They offer an optimization for production that ensures that you never ship more than the bare minimum of CSS needed. This means you can keep your palette of color, spacing, and other options fully enumerated in `globals.css` and elsewhere, without worrying whether you're using all those variants in production. Moreover, if you're working within a framework, such as Next.js, this minimization step automatically happens when you build, without even having to worry about whether it's happening. This alone is a compelling reason, at least for me, not to migrate from Tailwind.
Also, I've never found any restrictions in Tailwind in using inline CSS that weren't readily navigable, or in implementing really nice responsive grids that handle different screen widths for instance using Tailwind's grid tooling. I definitely have solved each of the scenarios described in this article using Tailwind or a Tailwind-CSS combination, but it's true that they don't have grid-column-areas natively. Still, I haven't yet found that to be a significant restriction in getting responsive grid layouts.
I think the biggest issue with Tailwind is simply that it takes a long time to get used to reading it. We all learn that inline CSS is bad, globally scoped CSS is best, etc., and we get used to seeing clean simple HTML. Then we look at real-world code featuring Tailwind and it just looks so hard to read at first, especially because the lines are so long. I guess I just have been using it long enough that I've gotten completely used to the way it looks, but I do remember it took me a very long time to get comfortable with reading Tailwind. After a long while, I concluded that, for me, Tailwind really is more efficient and maintainable and even more readable, but it definitely took quite a bit.
One approach I've been really starting to enjoy is to use use Tailwind alongside scoped styles (in Svelte and Vue). This keeps template pollution minimal while still allowing for the conveniences Tailwind brings:
For me Svelte and LLM completely removed my need for Tailwind. Turns out I was using it primarily to avoid CSS collision, and (to me) more logical syntax, rather than the self-imposed constraints.
Why did Svelte affect your stance towards Tailwind?
Presumably because you just put the styles in the component.
Yes and Svelte automatically namespaces them, so there's no collisions.
It bothers me that SMACSS is from 2011 and was completely ignored. That was the sane way to write CSS. Been doing that for more than 10 years and never had issues redesigning large applications.
https://smacss.com/
That site is a portal back to 2011, wow. Thanks for sharing.
Great writeup!
Lately I've been enjoying Open Props[0]. It's a library of CSS props/ variables that helps structure a design system. I like it because it's CSS-first, so like OP experienced moving off TW, I've learned more CSS, and it works with the browser not against it. It also provides some sane defaults for anyone less interested in fiddling with precise cosmetics.
[0]: https://open-props.style/
html encodes a tree of objects
css applies attributes to objects via graph queries
the queries are tightly coupled to the tree. you must work hard to avoid scatter gun edits now. it doesn't make much sense to have attributes stores in a separate location to their use
it would be like assigning all of your instances attributes using decorators
Tailwind crazy adoption is something that makes me happy to nowadays be doing mainly boring stuff in distributed cloud systems and agents, instead of WebUIs.
The part I’d miss most from Tailwind is not having to invent semantic-ish names for every wrapper, row, card, inner-card, card-content, etc.
I'm observing recurring patterns in Tailwind-only users: they learn a lot of non-transferable and bad habits, especially when the codebase scales up:
- Engineers never learn to properly use developer tools to debug CSS
- Components get gigantic bloated piles of classes that are not human readable
- Those gigantic piles of classes get logic in them, that often would have been easier to write as a CSS selector. Tailwind developers learn to write a JS ternary operator with a string of classes instead of ever learning how CSS selectors work
- Those ternary operators get too complicated. The engineers write object maps of Tailwind classes, or export consts of strings of Tailwind classes to use later. Those object keys and const names are what the CSS class names could have been if they just used CSS. They literally re-invent CSS classes, but worse.
- Tailwind classes can't be migrated. You can migrate CSS to Sass to CSS modules to Emotion CSS to etc mostly just by copying them over, because all of those are CSS (with some quirks). Tailwind classes are non-transferable
The happiest medium I've found was in an organisation of around 200 UI engineers: scoped CSS so that engineers can work with autonomy without colliding with other engineers, plus Tailwind for quick band-aid fixes.
> You can migrate CSS to Sass to CSS modules to Emotion CSS to etc mostly just by copying them over, because all of those are CSS (with some quirks). Tailwind classes are non-transferable
Tailwind classes are literally vanilla CSS classes. You can copy-paste their definitions directly
We also moved away from Tailwind — Pico CSS. It feels like a breath of fresh, modern CSS air.
We actually loved it so much that we’ve taken over maintenance of a fork, and just released our Pico successor candidate: https://blades.ninja/
It is really fun that the navbar has unaligned elements. (Docs is lower)
I'm so grateful that people are starting to see that Tailwind is insanity. Just write your class and your CSS as CSS. We don't need to pollute templates with endless hard to parse and reason about tailwind nonsense.
> I’m a lot better at CSS than I was when I started using Tailwind.
> I got curious about what writing more semantic HTML would feel like.
This is so relatable. In the beginning of my career, I used to add so many dependencies for things I did not know. But these days, I mostly work on removing dependencies because I'm a lot better at using the web platform. I treat the web platform and browser primitives as materials to build what I want rather than a blank canvas to paint things from scratch.
Oh, have we reached the end of the pendulum swing and are now swinging back to ordinary CSS again.
To be fair, CSS has evolved and improved A LOT in the past years, and a big reason why is Tailwind.
So, we're just going a full circle back now. Interesting.
_tailwind_ was the full circle. We had individual controls. We went to semantic controls, then back to individual controls a la tailwind.
Hope so. I’d like to be able to read the HTML templates again.
Yep. Every 7 years.
I'm lucky to have learned the web with Angular 2.x
It scopes CSS to components by default, and keeps HTML, CSS and JavaScript seperate.
Vue single-file components (SFC[0]) still works this way.
Very clean, easy to understand, and (as someone who started hand writing DHTML) it still feels very much like DHTML with more convenience and modern affordances.[0] Vue SFC docs: https://vuejs.org/guide/scaling-up/sfc.html
The only problem with Tailwind is its syntax. It is anti-CSS. It is confusing and takes time to get used to. Heck, people even wrote cheat sheets. That makes adoption wrong, because people install it just because everyone else uses it or because the current thing brings it in. A long time ago, before Tailwind, I was writing true, pure functional CSS https://www.fcss.club/manifesto, and I never came back to the old OOCSS or BEM style of structuring all the styles in a project. Components helped a lot with that decision. Today, functional CSS does not make sense only if your website is truly, truly simple. But if your application is more complex, FCSS outshines everything else: speed, rendering, simplicity https://www.fcss.club/syntax, and weightlessness.
> It is confusing and takes time to get used to
I don’t believe Tailwind is inherently worse than pure CSS. If Tailwind had existed from day 1 on the web and you had learned it first you probably wouldn’t say this. In fact, if Tailwind had existed first somehow, and someone came up with CSS as we know it as a new revolutionary library, I’m not sure it would have succeeded.
Well, the old-school way was something like this: h1.font.size = 24pt 100%. Tailwind has its own syntax, so you need to learn it first, which adds extra cognitive load. For things like * { color: } in Tailwind, you have multiple options: text-color, stroke-color, etc. With FCSS, you simply have color--[colorname] { color: … }. It’s simpler and avoids unnecessary cognitive load.
Yeah learning syntax is not rocket science, using that against Tailwind is not a valid argument IMO
I used Tailwind in one web based product and it was alright, but I'll never go back to using it.
The HTML bloat was really tough to deal with. I spend far more time in HTML than I'd like, and having more Tailwind classes than I do semantic HTML was really tough to look at.
I've settled on using vanilla CSS and applying styles per-page on an as needed basis. For example, include base styles (reset, primary theme, etc) and then include marketing styles (or: blog styles, dashboard styles, syntax highlighting styles, charting styles, etc).
It keeps each page light and minimal. Reading the HTML is easy. Styles stay consistent across any pages that share styles, etc.
IMO the killer feature of tailwind is that it lives alongside your React components so you keep things DRY, and you get a design system with type and spacing scales out of the box. It’s a form of constraint that helps create structure. But I think that makes it a victim of its own success. The tailwind spec becomes ever more complicated the more native CSS features it tries to include. I’ve seen tailwind incantations go way beyond editor wrap line
I still like it though. it’s one of those abstractions that actually helped me learn. I would go to the tailwind doc pages and see the underlying css of any class.
There were some other frameworks I got excited about: vanilla extract and stitches, both made by some really talented people. I wonder why those never quite got the same traction…
tailwind is an anti-pattern that breaks separation of concerns rule. i'm amazed how it became so popular.
Because it’s a made up rule, not a law of nature. Having the styles inline on a component is very easy to reason about, it was just extremely impractical with CSS and the style attribute. Tailwind makes it easy, practical, and is actually well designed regarding its support for variables (ie. you can have a sane design system easily).
What you want to share/cascade is variables, not styles. Styling components makes it easy to make sure the styling of each component is isolated and doesn’t have unintended cascading effects. When working this way, using Tailwind is as much a good pattern than, say, CSS modules (which I like too).
The elephant in the room is AI. The frontier LLMs seem to prefer Tailwind by default. And they do a good job with it--likely because coupling style definitions to your HTML document structure is the least overhead way for an LLM to write code. The alternative--reasoning about a platonic ideal CSS structure, mapping the document structure to that an inferring how the layout will look, then juggling all that while making iterative edits--is a lot more "cognitive" work!
Tailwind makes sense when you can use React Components to build a reusable design system. It is annoying as heck to use if you don't have that superstructure.
To add: I dont want to go back to writing semantic css.. no thanks - I dont go in time-back machines. This is a mess.
Just using tailwind and anchoring around a design system like shadcn is just way easier for a team to align around than somebodys made up css language.
I just want to point out that you can use Tailwind inside your CSS with the `@apply` directive (not to be confused with the since abandoned CSS `@apply` rule). You write your CSS and mix in Tailwind instructions where it makes sense. Example:
I used to be a big Tailwind hater because putting all those utility classes as inline styling into my HTML is a crime against nature. But this way I get the best of both worlds. Tailwind is really nice as higher-level building blocks and saves me from writing a bunch of media queries.> putting all those utility classes as inline styling into my HTML is a crime against nature.
It’s really not when working with components instead of pages, and when working with variables properly
Even with components I prefer what Astro is doing: the component can have a `<style>` tag in which I can add my own CSS. When building the website Astro will know how to transform the CSS so it only applies to that component. This way markup and presentation remain separate even if they are within the same source file.
https://docs.astro.build/en/guides/styling/
TFA links to https://joshcollinsworth.com/blog/tailwind-is-smart-steering, which is about Tailwind, but makes multiple distinctions and points that could just as well apply to LLMs, e.g.:
> Builders value getting the work done as quickly and efficiently as possible. They are making something—likely something with parts beyond the frontend—and are often eager to see it through to completion. This means Builders may prize that initial execution over other long-term factors.
> Crafters are more likely to value long-term factors like ease of maintainability, legibility, and accessibility, and may not consider the project finished until those have also been accounted for.
> In my view, the more you optimize for building quickly, the more you optimize for homogeneity.
I am so happy that the only time I have to touch css anymore is for simple internal tools and pico is usually enough for them.
+1 for Pico CSS! It’s fantastic.
We actually loved it so much that we've taken over maintenance for a fork here: https://github.com/anyblades/pico
A life framework, consensus, that how the man face the thing that they crafting.
Author in 6 months:
How I refactored my rats nest of CSS back to tailwind.
What I don't get about tailwind is: why not just use the style attribute at the point?
With Tailwind:
With style: Now more interestingly, Tailwind with hover and focus styles: That’s not possible with the style attribute.Even more interesting with Tailwind, a div with dark mode and responsive styles:
That’s not possible either with the style attribute.Now your first instinct might be to "that’s unreadable", but keep in mind HOW you actually read and write this code. You’re not actually reading it to understand what it does like you do with iterative code. You see how the browser renders it, and you just adapt the code. Tailwind code is mostly write-only and maintained by viewing what the component looks like. This code doesn’t need to be reusable either, the whole component needs to be. The Tailwind code inside is unique.
It's much more verbose and can't do everything Tailwind can anyway.
E.g. how do you style a child on parent hover with the style attribute?
I wrote my first CSS 20 years ago, and one thing I can say for sure is that it's impossible to really structure your CSS in this way. The structure will break down over time and it will anyway be buggy and you'll be chasing your tail, as long as everything you do is global. It might work if you're a solo developer, but the reality is most projects involve multiple people trying to get things done. As long as a style is global, changing it will break something else. These days I use scoped styles and that's it.
My favorite is when colleague A broke something from colleague B, who fixed it but broke sometimes from me, and I fixed that and broke what colleague A did. The process repeated once more and it landed again on my desk, where I said wait a minute, I've been here already. We were than able to fix all three things at the same time.
So it's difficult to keep track of everything.
Nature is healing
oh is this stupid hype of defining design in html with a random framework finally over? thank god!
Can we stop with this kind of trash opinion? Do you genuinely believe people use Tailwind because they don’t know any better? I understand why many people have a visceral reaction against Tailwind when learning about it for the first time, but when you actually use it, you really can understand why some of the "anti-pattern" stuff you learned about CSS doesn’t really apply with Tailwind.
Thats why I maintain the successor to tachyons: https://tachyonsneo.com No build pipeline. Resort to CSS when utilites make no sense. No lock in.
Can you clarify how this relates to Tachyons - do all Tachyons features work? Or is it a subset of Tachyons?
The purpose of web is to display various things and let the user interact with them.
Nobody cares about true REST (modern day RESTful is a different thing), HATEOAS or semantic web. People tends to simplify things.
We probably can live with just 7 html tags,<title>, <style>, <div>, <form>, <input>, <button>, <a> and CSS.
Relying on React or Typscript in LLM era seems very stupid, just have the LLM setup whatever dom manipulation you want and have it write decent JS without slop. Far more offline compatible development almost negligible supply chain issues as well. At least ones you can control.
Layout design issues are orthogonal to choice of language and framework. You can apply the article's approach to plain static pages and to SPAs.
I tend to work closer to the latter end and find that both React and Typescript are extremely helpful to make my code extensible and maintainable. YMMV.
This makes no sense. LLMs and agents benefit from (good) abstraction as much as humans do.
This works great for small sites/apps, but really starts to fall apart if/when it gains complexity where React starts to make sense. I've tried a few times to "just use plain javascript" with the LLM and initial results are often much better, but if the site grows a bit too complex, the LLM starts making a lot of mistakes and it can be hard to reason about as a human and get it on the right track. That hasn't been the case with the React apps IME.
React maybe (because there are alternatives).
But for mid/large projects, I find that TypeScript brings sanity to JavaScript.
I love some quick and dirty JS project. But after a certain project size I begin the see runtime errors like undefined, NaN, 'false/true' concatedted to URLs, and so on. TypeScript eliminates a ton of those.
What a strange take. LLMs produce plausibly correct output, which is exactly where plain JavaScript and DOM manipulation will result in a spaghetti mess.
Frameworks like React that add structure to the data flow, component encapsulation, and a huge repertoire of patterns to train on, plus Typescript for immediate compile-time feedback loops… those are what LLMs thrive on.
I agree, specially for simple apps. it's much easier to upgrade if you are not relying on 3rd party or NPM's. Don't have to worry about code injections.
I have these two https://reddit.premii.com and https://hn.premii.com/ both works without any changes. Reddit will stop working once they kill the apis but until then it will work.
Why write in rust if LLM can write assembler for any architecture?
> just have the LLM setup whatever dom manipulation you want and have it write decent JS without slop.
Hmm, yes, simply have the LLM write good with no mistakes.
I hope I never have to work with you.
I think the (misuse of) so-called "separation of concerns" has been the most harmful thing that happened in web front end development. HTML can CSS are the same kind of concerns: the presentation layer. The idea that HTML is purely semantic and has nothing to do with presentation is just burying the head in the sand.
Separating HTML and CSS into different files is just like separating a bunch of methods/functions into different files, or splitting one monorepo into git submodules. Yeah, it sometimes makes sense, but if you're doing it for the sake of separating things then just stop.
I think the only point of Tailwind is to make front end devs realizing how much separation of concerns is misunderstood and misused as a dogma. Once you realize that you can ditch Tailwind if you like.
HTML is the content layer. CSS is the presentation layer.
Yes, this is the exact harmful idea that I was talking about. If this were true than there would be no 'reader mode' in browser. Or the reader mode would not modify the html at all.
What you see in reader mode is the content layer. HTML is a part of the presentation layer.
Are you arguing that things like headers and navbars (which reader mode hides) are part of the presentation layer, not the content layer?
There are different types of separation of concerns :) https://x.com/simonswiss/status/1664736786671869952