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.
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.
Once upon a time, whenever I interview developers, most of them proudly announced their expertise in jQuery. I have to bring them down to the basics and ask them about JavaScript. Almost all of them were lost. I asked them, if not for this, but to learn JavaScript and all the other framework will that; a framework on top of JavaScript which one can just use (perhaps take a week or so ro learn).
The same goes for CSS. Everyone bolded, and highlighted their experience with Bootstrap but missed the CSS. I did used Bootstrap, Foundation, Skeleton, Bourbon, and many others, especially when working with the team, so we all can speak the same language. This is true for Tailwind too. I remember when Tailwind was still in alpha and I realized that was the perfect tool to bring the team together and move fast. I was able to use it both as a utility and like most other people as the HTML polluter (but it worked).
If one is keen, it is always a good idea to learn the core - HTML, CSS, JavaScript; all the frameworks that wraps them should just be syntactic sugar. Bootstrap came and went, so will Tailwind.
PS. With AI/LLM Coding Assist, writing in plain CSS is becoming beautiful again. I can outline what I want, give it a checklist and make it do the strenuous part of writing them. I don’t even have to remember the cascades.
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.
I have found some nice middle ground with Frankenstyle [0] framework which take some inspiration from Tailwind (and Shadcn) but with no-build, which is the thing that I found really annoying with Tailwind. CSS with a build process is already not css anymore. But I understand the feeling of OP to want a cleaner code - without sparkling style details all over the html, though overtime I do feel it really gets out of hand and in the end having the styling in the html makes it less complexity to manage which class is where.
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:
<div class="counter-component">
<button @click="count++">+</button>
<span class="count" :data-is-even="count % 2 === 0">{{ count }}</span>
</div>
<style scoped>
@reference "tailwindcss"
.counter-component {
@apply flex items-center gap-2;
button {
@apply bg-gray-800 text-white;
}
.count {
@apply italic text-teal-500;
&[data-is-even="true"] {
@apply text-rose-500;
}
}
}
</style>Tailwind isn't just a framework; it's a design system.
The creators of Tailwind wrote the brilliant book "Refactoring UI" [0], which presents a systematic design system, introducing ideas such a type-scales, color-scales, spacing, and tactics to minimizes the cognitive burden on the designer by forcing design choices. The ideas presented in this book basically are tailwind classes!
When you build with Tailwind, everyone is speaking the same design language, and you end up with harmonious designs, even when components came from different projects or designers.
I disagree with the author's approach. It's basically just copying the utility classes from Tailwind and implementing them in an unergonomic way. Perhaps the best idea is component level CSS which is something that's been enabled by better tooling. I would implore anyone who doesn't really get Tailwind, to read the origin story [1].
[0] https://refactoringui.com/ [1] https://adamwathan.me/css-utility-classes-and-separation-of-...
Two great things about Tailwind:
- AI already have data about its classes in their training data - No conflicting styles
This means that AI doesn't need to reference any existing stylesheets when generating new styles, which is great for context management.
With custom CSS, you'll have AI read existing stylesheets because otherwise its going to write conflicting styles or rewrite stuff you already have. This can be a problem if you have large stylesheets that take too much space in AI memory.
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.
From an accessibility point of view and working on an accessibility audit, using tailwind and utility classes can be really difficult. Working with tools like Siteimprove, will ask if there are matching selectors for things that may be similar, and idm there aren't you may find yourself, looking at each page separately, with that component in it.
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 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.
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
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.
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.
Great stuff, one thing I tend to do is set the body font size to be 10px but in ems so all calculations can be made easily - body { font-size: 0.625em; // 10px } and then if you want a heading to be 20px you just do h1 { font-size: 2rem }
Oh, have we reached the end of the pendulum swing and are now swinging back to ordinary CSS again.
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/
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.
So, we're just going a full circle back now. Interesting.
> 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.
For me, the main thing I love about Tailwind is that I don't have to create ad-hoc class names. No more BEM.
tailwind is an anti-pattern that breaks separation of concerns rule. i'm amazed how it became so popular.
Always happy to see a Tailwind/no-Tailwind flame war.
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.
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.
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…
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.
This feels like reinventing Tailwind, but worse.
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.
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.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.
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.
A life framework, consensus, that how the man face the thing that they crafting.
Nature is healing
What I don't get about tailwind is: why not just use the style attribute at the point?
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!
Author in 6 months:
How I refactored my rats nest of CSS back to tailwind.
oh is this stupid hype of defining design in html with a random framework finally over? thank god!
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.
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.
[flagged]
[flagged]
[flagged]
[flagged]
> 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.