Wasn't the whole point of CSS to separate presentation from data, and move away from <font color=red> and other <table> that made layouts too difficult to change? Consider this:
<h2 class="text-purple-500">Customer Support</h2>
How is this different from <h2><font color="purple">Customer Support</font></h2>? This is still considered bad practice, right?
> "Best practices" don't actually work. I’ve written a few thousand words [0] on why traditional “semantic class names” are the reason CSS is hard to maintain, but the truth is you’re never going to believe me until you actually try it. If you can suppress the urge to retch long enough to give it a chance, I really think you'll wonder how you ever worked with CSS any other way.
As another example of "am I crazy? No, it's the entire rest of the internet that's crazy" check out htmx [0]. It's another acknowledgement that best practices (for js / ajax) are not best in all circumstances, and has been a fantastic fit for most of my web projects.
In practice, "the right way" is shorthand for "the right way for your project", and conventional wisdom / best practices are vital to know and understand so that you can recognize when they are a bad fit for what you need to do.
Other than "inline style bad", when the first one is built into the browser, and a second one requires including an external dependency, that I imagine contains classes w-1 to w-1000 or whatever. Now I strain my IDE-s autocomplete with 1000s of classes, will need a css minifier/bundler (that will at least make my build slower).
I'm not a frontend dev, but I do dabble in writing the occasional website, and other than bootstrap (which contains complete styles for stuff like buttons), I never felt the need to use a CSS framework. I get it that in pre-grid days, it was a minor feat of wizardry to implement complex layouts, but nowadays, frameworks seem largely unnecessary to me. Another added benefit of using pure CSS, is you just have to learn it once (with tons of great resources), while with these CSS frameworks, I'll need to learn what everything's called for every framework.
CSS classes can work with media queries while inline styles cannot. A ton of the classes that are generated are screen size variants.
The IDE strain is overblown. There is an official Tailwind plugin for most IDEs and it performs well, even with the massive CSS files you use for development (but are purged/tree-shaken for production). Computers are really good at parsing text, and even at their worst, the CSS files are never more than a MB or two. Even if you enable ever single flag for every token, the dev file size is around 10MB I think. Big to load over the internet, but locally, and being parsed, not a huge problem at all.
There's a section of Wathan's article, "Isn't this just inline styles?" That directly addresses this question. Perhaps you missed it?
> With inline styles, there are no constraints on what values you choose. [With utility classes] you can't just pick any value want; you have to choose from a curated list.
If the list is so long that the whole thing isn't even included by default and you need to custom configure which entries appear on the list, it's not that much better than arbitrary values.
except that w-24 is not 24px. It's 24 units of whatever your design grid says, so now you can use a design language to describe your layouts instead of absolute dimensions
Other people have noted that your `style` example isn't responsive, but on top of that, there's nothing stopping you (or a collaborator) from writing `style="width:26px;"` somewhere else in the app. Having a restricted set of options helps enforce consistency across a project and gives you a much nicer look by default. This is especially useful for devs doing frontend work without a strong design background.
Nice article. It manages to explain the uneasy feeling I've always had when people authoritatively talk about "decoupling HTML and CSS". The coupling is somewhere, and where it is causes tradeoffs that need to be considered.
I agree with you, Dan, and Adam Wathan for that matter.
The point of my comment had more to do with front-loading of a pitch with copy specifically tailored for confirmation bias.
Running to achieve weight loss can be hard. So much so that it's intended value can't be realized, or even be detrimental to the end goal. But I think we'd agree that anyone who simply says says "Running won't help you lose weight because it doesn't work, try this instead" is not seeking to help you, just sell you.
This was also React's initial pitch as well: getting DOM updates right is hard, use vDom instead!
The truth is that the majority of frontend development is devs racing to shovle the latest stack of tickets that management cooked up with little collaboration or rhyme and reason into the Done column of JIRA (web dev is particularly imperiled by shitty lay management), any DX affordance they can get their hands on is valuable to this goal
Concurrently, designers are pumping out comps as fast as possible to match pace with no real system cohering the whole thing, you need a design system to provide an "abstract interface", so to speak, for your CSS to really transcend into real separation.
This is just what I've observed working at my comp
I guess that's falling into the same trap people fell into when they rejected React because the html was wired directly to JS. There's now component level isolation that means you're not working at a global level anymore, and you can reuse those components.
The issue with CSS has always been that you create these lovely generic styles, and as time goes on it becomes harder to make changes without breaking other components. Despite good intentions and large amounts of effort, things become coupled.
The issue with your example above is that it doesn't handle all the cases. You can't deal with responsive layouts or hover states etc.
Tailwind addresses these 2 issues. They reject global stylesheets by working at the component level. You can change a component knowing that you won't break anything else. By using classes, you get to have all the substates configured locally without having to use global stylesheets.
A lot of what's happening in the CSS world now is about ditching the global cascading nature of CSS and trying to isolate styles. Like it or not, that's the direction it's moving, for very good reasons. All the CSS-in-JS solutions have tried to do it too, but honestly, for me, the dev experience sucks.
> Wasn't the whole point of CSS to separate presentation from data
Well, no, not really. It was intended to stem the tide of HTML devolving entirely into a presentation language, but H.W.Lie's thesis[1] makes it quite clear that HTML was always expected to remain a middle ground mish-mash of structure, content, and presentation, one in which everyday authors could still intuitively grasp the purpose of standard elements, and over which CSS would then supply some contextual visual order.
The fundamental notion of CSS is a rich and versatile and non-Turing-complete set of overlaying structural selectors, a design somewhat in reaction to the perceived inapproachability and abstraction of DSSSL; something that still pains me even two decades after that battle was lost.
In my experience it has a lot to do with the scale of the project. Beyond a certain size (lines of code, length of time, number of people, etc) semantic markup starts to pay off. But if you're just trying to put together a prototype as quickly as possible, these kinds of frameworks usually speed things up because you don't have to think about proper semantic organization. I've worked on multiple projects where something off-the-shelf like this was the way we started, then at some point as the team grew we re-organized the CSS to be semantically defined. Yes, there is some amount of waste, but this pattern tends to optimize for the things that you need to optimize for in the moment. No sense spending time to carefully design your semantic abstractions until you know the project is going to be a large enough scale to make it worthwhile.
I would say it is the opposite. Beyond a certain project size, semantic html + css becomes unmaintainable because you are not able to predict what while change if you remove one line of css.
That was sound advice back when we were primarily writing HTML or HTML template language-like things.
Now that people are writing "components" in frontend languages , it makes more sense to reason about styles in terms of those components. That's basically the insight that Tailwind had - can we get the benefits of componentization without resorting to CSS-in-JS solutions? The answer is yes. And we can even bring it to other development stacks by using Post-CSS
My take on this is that documents are different from applications, and so different approaches to composition and organisation are arguably OK.
Personally though, I'd introduce another intermediate set of names - like 'color-brand-primary', 'color-brand-secondary' etc... so you can make branding changes more easily. The majority of Tailwind can be used as-is though.
It's definitely feasible to add custom color names (and other properties). I have brand specific color names in an app I'm working on right now. There's a theme object in tailwind.config.js that deep merges with the defaults. Great for setting up custom colors, fonts, etc.
I get it; there is more than one way to do something--especially in code--and what feels correct for one person may leave a bad taste with another. It's just that nobody has been able to explain to me yet how JSX, Tailwind, etc... are overcoming the complexities that such mixing of concerns introduced before those projects came along.
Let me try to explain: in the past, best practices dictated we should separate things by language because it was the best we could do at the time. Also it allowed for "skinning" websites, which was nice.
But after a while we noticed that wasn't enough, giving rise to CSS methodologies like OOCSS and BEM that aimed at separating CSS by components. We also started doing the same with Javascript, and even HTML using helpers or smaller "per-component-templates" in backend frameworks.
Now, we have components, and that is your "separation of concerns". Modern frameworks like React and Vue allow you to have even smaller components, which means less code. Mixing languages stopped being an issue for most programmers.
If you're still not comfortable, you can still do it: in Vue.js it's quite trivial to have separate JS/CSS/HTML files. But most people have moved past that since small components give them the separation of concerns they need.
Also, remember the skinning part? With SASS/SCSS/native variables, you can now abstract your CSS and keep them closer to the code without losing the possibility to change the appearance.
As to Tailwind, Bootstrap had already paved the way for helper classes. Lots of people used them in a non-components websites and it worked for them. The only issue they had was that helper classes require some copy+paste that would make maintenance difficult. But now that we have good component systems it's not a problem anymore, and Tailwind is pretty good for maintenance.
Also, regarding skinning: with a Tailwind-like frameworks you also get some help with this, since it's the same primary-color class all over the system.
EDIT: Another great argument I read here at HN is that even with Tailwind there is still a separation between content and presentation. It's just that before my "content" was in my HTML and my "presentation" was in my CSS. Now, my "content" is in my JSON while my "presentation" is my HTML/CSS. Tailwind as an extension of HTML makes a lot of sense in this context.
That philosophy works if you have a lot of docs with uniform content that can be represented by simple tags that don't need to change much. However if you have a rich UI then A) there tends to be more structure than content and B) structure, behavior and presentation are coupled together by definition.
Yes you can try to shove the square post into a round hole by naming your HTML just-so, cramming as much presentation in your CSS with pseudo elements, and using pure JS files hooked to HTML via classes/ids, but the minute you need to change anything you'll see you can't make a single change without carefully considering all 3 "de-coupled" file types. Worse, if you try to reuse these semantic definitions across a large app, now you find that a change over here can break things way over there, the larger the app the worse it gets. Once you get to this point you realize that component level isolation ala React/JSX is a much more sane approach to manage huge web app UIs than pretending that functional de-coupling is some kind of platonic ideal rather than just one possible set of tradeoffs.
I felt this way too until I started using Tailwind. I feel like Tailwind (and perhaps atomic css in general) is one of those "don't knock it till you try it" kind of things.
But, with that said, I do feel Tailwind relies on components (such as React, Angular, whatever) to be most effective. You'd ideally stick that purple class in a Heading component and maintain consistency that way.
Been through this process. Thing is, when you have a <Heading> component, you might as well simply write a css module for that component. Tailwind is great for "fast prototyping" and quickly styling a bunch of static HTML though. Wouldn't use it for a larger single page app.
I might be overreacting to past projects, but I've almost always found bespoke css gets out of control. Most projects tame it with well defined values for margin, padding, etc, and applying and enforcing those values means you sort of end up creating your own Tailwind. The "atomic" side of Tailwind is nice, but so is the consistency in values it provides.
If I was to take the css module approach today, I might still find myself using Tailwind and @apply inside the modules.
it's different in specificity for one. you can also extend the config and create a color called `branding-primary` for example, then use that for anything you need to apply a color to. can do similar with css custom properties or sass/less variables
You still want the ability to customize the size and color of all your second-level headings in one place.
The font example you listed got popular because it was easy and no alternatives yet existed, but became a burden when it got baked into thousands of separate files. Hence the rise of separating markup from presentation.
Now the pendulum has swung the other way, and that original ease of dropping styles right into markup looks quite attractive, while the separation ethic seems overbearing. React's component-based approach has also made it easier to centralize presentation while still leaving it mixed with the markup, which has given Tailwind and similar methods space to edge their way back into mindshare.
I'm still old school enough to appreciate the ideal of the separation of CSS, HTML, and JavaScript but one benefit I do see of the Tailwind approach is that refactoring is a lot easier if it's all in the class attribute.
Let's say you suddenly want all text-purple-500s to be normal weight and red, it's a simple search and replace as opposed to old school HTML where you might need to remove a b/strong tag, etc. It's not exactly elegant but it is at least viable to do quick search and replaces for styling than using presentational tags.
For me, it was trivial to implement a color selector. I basically never use `text-{color}-500` but instead change mine to `primary` `secondary` etc. (I do have some for grays, though).
You can check out my color selector + dark mode selector at the top of my site here:
<h2 class="text-purple-500">Customer Support</h2>
How is this different from <h2><font color="purple">Customer Support</font></h2>? This is still considered bad practice, right?