Author here. I implemented the commercial Tailwind "Spotlight" template with Semantic CSS and compared the differences in weight, amount of HTML and CSS, rendering speed, and best practices. I was surprised to find _that_ much overhead in Tailwind. Curious to hear your thoughts.
Thanks for the thought-provoking comparison! I have a few questions:
- Did you compare both versions of the page for feature parity across multiple browsers, devices, and breakpoints?
- what exactly accounted for so much bloat with the tailwind version of the site? Was it the CSS itself, or the classes that accounted for most of the difference? If it was the CSS, did you optimize it as per Tailwind's docs[1] so that unused styles weren't included in the final page styles?
- How much longer (if at all) did you take to design your selectors for the semantic version in order to reduce repetition, than you might have taken for creating the template with tailwind?
- How did the final sizes compare after brotli compression?
But tailwind/nextjs version has a lot of files incluced (ie. svg icons) which the other extracted to separate files.
Also, nextjs adds its own code that is completely unnecessary. Such as, 15 reponsive versions of the same image file, script tags at the end with the whole content in json.
This comparison does not feel objective (or honest) at all to me. If you want to prove to professionals that your CSS solution is better, you need to provide much stronger evidence. Preferably ones those professionals can't disprove within 30 seconds of comparing the examples themselves.
True. There is some unnecessary next.js clutter in there, which I'll address on my next post. Removing that would make the HTML leaner. Likewise, removing the inline CSS from the semantic version would make it leaner.
Did or can you publish a small snippet of how Nue-CSS would be implemented in or with Nue?
Can't wait to use Nue for my next project, currently wait for CSS implementation (even if it is not necessary for Nue). Project is currently in design phase, so i have luckily time.
Glad to hear this! I'm updating the create-nue project next. It will clarify a lot of things. CSS pre-processor is the next project to be published. I don't want to give ETA, because I'll likely disappoint.
- What exactly do you mean by "Semantic CSS"? I've never heard this terminology before (might just be OOTL). I get the parallel between this and Semantic HTML, but I guess it's not as clear what it is supposed to mean for a styling language to me. Is it just native CSS (or "pure" CSS)? At least, as a result, I have no idea what this means, or by what measures you decided what constitutes "semantics" in Tailwind's CSS:
The most surprising thing is that Tailwind uses more global/semantic CSS than the semantic approach itself ¯\_(ツ)_/¯
- A small nitpick, maybe: could you somehow replace the top links to the two versions you compare with actual HTML links instead of JS events? This prevents middle-clicking and the location is replaced so it's tough to compare the two sites while reading your article. I have to click on your link, copy the url, and open that in a new tab. At least, opening the page in a new tab would be nice.
Thanks! Semantic CSS describes an elements meaning clearly: like <form> , <table>, or <div class="gallery">. Tailwind is non-semantic because you cannot say what the element does, like for example:
Tailwind templates are extremely verbose, because they feel it is worth to spend their time optimizing features and adding designs to trying to make their code smaller.
I have often used their components as a basis, and clean up huge chunks because I feel that is in my interest for future maintainability - it is not a fault of Tailwind, but a decision they made.
You can make a "semantic" design , using Tailwind where it will save you tiem or add readability, and get the best of both worlds.
I find Tailwind really good for prototyping designs and iterating quickly, and as the design becomes more crystallised then I moved to semantic css and start to clean up the complexity. Once I've figure out that patterns and components required...
Yeah, cause I end up with like 5 different buttons designs using Tailwind, and they all have different classes but when I clean up the code later I reduce it down to one button design that fits the final theme.
I figure most startups would build an MVP in two weeks and then rebuilt it later or move onto the next version and clean it up a bit in each version that follows.
Never change a running system. If you create a working project, you will not change it, especially a startup.
Possible on a later bigger update (if necessary), a complete app would be rebuild. But here is the same problem: why should you do it, if your project works?
I personally had not seen such an update in early stage of a startup. Did somebody have insights or examples?
I’ve found myself wanting a Tailwind compiler that makes it easier to work like this. Iterate fast with maximum verbosity, but later extract common patterns to classes when things are more stable. Anyone aware of any tooling like this?
That would be nice, like a Webstorm / editor feature that detects when you re-use code and recommends abstracting it. I would love that to be able to detect that like "Hey these buttons all share these features, let's refactor it for you"
Go for it! At the very least there are some interesting algorithmic problems in there. I was leaning towards calling it "Leaf Blower", because it's a form of wind that tidies things up. Take it or leave it. :P
To me you are adding complexity. Back to finding which styles are cascading over another.
Tailwind is somewhat like lisp in the aspect that people usually don't get what all the hoopla is about. All they see is parenthesis and want a "lisp" without the parenthesis once they figure out polish notation
This is one of the first Tailwind vs * articles that isn't bad. It makes a good case in the analysis.
For a lot of applications (like a blog), what the author calls "semantic CSS" is the right thing to do. But there comes a point at which it is no longer feasible. We have a long history of SMACSS, object oriented CSS, BEM, CSS Modules, scoped CSS, and now Tailwind to make it work.
> Because mastering CSS requires practice. It takes several failed attempts before you get it.
Explaining the popularity of something by simply proclaiming "git gud" isn't a very strong point. For newer CSS devs, it does take away the woes of clashing naming and a badly structured cascade. The rest remains and the results remain shite. Similarly, I haven't seen a pattern where good CSS authors do not like Tailwind.
No this article is flawed. It fails to recognize the fact that css and html are always coupled in one direction or another. With tailwind the css is fixed and the html is designed around it. With semantic the html is first created and then you write your css around it.
The fact is that updating css in a big project and a big team is very difficult. Rules are scoped globally. It only takes a junior making a few design mistakes
and now you don't what you are going to break if you update anything.
With tailwind the css is fixed and will never change. So you just change your html and you know what to check/what to test again. For any medium to large project this is a big QA & time boon.
"just use code review, naming convention, xxx best practice". This argument is similar to "just don't make mistakes". Mistakes will be made. With semantic css you will suffer.
Author here. You are right: HTML and CSS are always coupled. The major difference is that Tailwind embraces tight coupling and semantic CSS embraces loose coupling. Please check the "Best practises" section:
This section is what I am talking about. You compare two methods of writing components and then declare that the tailwind version is tightly coupled but the semantic version is loosely coupled. In programming lingo this means tailwind is bad and semantic is good.
But you don't explain why the tailwind version is tightly coupled and the semantic version is loosely coupled. And you don't do it because it is simply not the case. The coupling between html and css is not tighter or looser. It just goes in a different direction.
> The semantic version, allows you to change the design of the gallery freely. You name the component and style it externally. With Tailwind the style cannot be separated from the structure.
Same with tailwind. You update your component to update its style. With semantic you can update the css rules without touching the html, but you don't know if this css change won't break another part of your design. If you have a simple html structure like a blog with very few components, then semantic works (but so does anything really). If you have lots of components and you need to update them in order to add new features, then semantic will bring more issues.
So `<div class="gallery">` is loose coupling, because the styling is not coupled directly into the element. You can completely switch the gallery design, by switching (or overriding, or modifying) the external stylesheet.
The coupling is in the right direction: designing possibly very advanced CSS to get a desired appearance from a given good markup, instead of compromising markup to simplify CSS.
The issue of this direction is if you need to update the markup, you will need to update the css, which is hard and risky because of css global scoping.
New and updated CSS rules should be usually needed for new "themes", exactly the type of change that semantic markup is robust against (e.g. placing image captions in a sidebar rather than below the respective images), and for backwards compatible extensions of the original design that add support for something new that will only be used in new markup in new pages (e.g. allowing small images inside paragraphs, meant to be displayed inline, in addition to large ones between paragraphs).
What kind of fragile markup and problematic CSS global rules are you worrying about?
> What kind of fragile markup and problematic CSS global rules are you worrying about?
I’m wondering that too. If I’m writing a SPA, styles are usually scopped to the component with a base styles for UI atomic elements. And some conventions around spacing. If it’s a website, it will be separated in layouts and small components Bootstrap-like. An update will be identified as a variant or a special case.
As for coding this, search all files is usually a godsend when refactoring. You can use ripgrep if the editor does not have a good implementation.
A lot of applications have a long lifetime. They are not shipped only once. If I want to add a new feature, say allow users to perform a new operation in an existing screen, I will have to update a few components for this.
You want to preserve the theme an consistency of the UI, but adapt its functionality. For this type of changes the semantic version is problematic.
Ordinary changes fall within the boundaries of the existing design system: for example, "a new operation in an existing screen" probably means adding instances of a few existing components (button, paragraph, title, section, pop-up submenu, etc.), with no impact on styles, not "updating" components.
I can't, for the sake of me, understand how some frontend devs still complain about CSS' global scope when style encapsulation exists and is widely adopted in a form or the other, either native (Shadow DOM) or emulated.
You don't like global styles? Don't use global styles, period.
Even without adopting Nue and staying on React, CRA and Next comes out with CSS Modules out of the box. Angular and Vue have their own equivalent.
I have rarely seen this work in practice in 20 years of CSS, but maybe in CSS Zen Garden. Yes, you can freely change the CSS. But usually changes are triggered by the HTML, like some information is added. This then requires you to change the CSS and likely break all sorts of other uses, which is not a problem with Tailwind.
hmm maybe IDE plugins can solve this?
(eg. add a 'hover to show css' or 'expand css if I press ctrl+alt')
as for tailwild, I just can't bare its wide-ness - too many className attribute string going over 150~300 char widths, with some ternary operators mixed on top...
(seems vanilla-extract / ete have better DX, with occasional inlining for immediate deadlines)
The only way to know is to go to the page and inspect it using devtools. No Ide will be able to infer which rule is going to apply to any given element.
But the problem is that you need to make sure that a given css change is going to affect only a specific set of component. So you need to check all components. Since it will be too time consuming, you will probably skip this step and hope for the best (and do some QA to check that nothing is obviously broken).
It is still coupled because the css will need to know the html structure in order to work. If you update the html, you probably need to update the css.
Both of them need to conform to the same allowed patterns of markup. I think we lack a kind of markup schema or type system that everything can be validated against, where we can verify that our design takes every state and permutation into account, and that both styling and content implements their part of it correctly.
You will be more effective if you deploy your words more carefully.
Loose coupling vs tight coupling has an established meaning ≈ “if a change to module X causes a failure in module Y, then we say X and Y are tightly coupled.” This has some “knock-on” effects that you are trying to gesture at, like, “If two modules are tightly coupled then because of that tight coupling we usually have to coordinate deployments between them both.” And then you're like “I can ship new CSS without having to coordinate a deployment of HTML, that must be loose coupling then!”
But this is a sloppy way to use the language, because in the one case you have one module, and in the other case you have two: there is no coupling in the Tailwind case because there is nothing to be coupled because it all ships as one module, then you construct a more modular approach, one with two modules in fact, and these modules are (depending on how you squint at it given that we are doing decorative programming and there are no explicit errors) in fact “tightly coupled”—the semantic HTML essentially provides a customized API which the CSS uses by hooking into; this is a common form of tight coupling in microservice deployments for example.[1]
So you are actually looking at, one module plus a static asset that defines a common language, vs. two modules that are tightly coupled, and declaring that the two modules are “more loosely coupled” and this is somewhat of a nonsense thing to say. Like I get what you were going for, I didn't write this as a top level complaint because I understand what you meant, but here in the thicket of comments I see that you are not communicating well with your interlocutor because of this sloppiness and language, you might want to pivot?
[1] This point about microservices is likely to outrage a random passerby, look I am sure that your microservices use RPCs against HTTP APIs in a way that doesn't cause you much grief in having to update multiple modules in one commit (if monorepo) or stage “topics” (Gerrit’s term) where you carefully have to merge 2 or 3 pull requests to different repositories all at the same time (if multi-repo). But even though your microservices are loosely coupled, I have worked on very similar architectures where we did have to stage topics and if you didn't then changes to service X would break service Y, “We’ll ship a new value for this enum, that requires changing the common schema repo, which requires updating every microservice that looks at that enum so that it doesn't have a deserialization failure ‘this is not an allowed value for that enum’ when the new values start rolling out, the ones that actually switch() or if() on that enum need to be updated to do the right thing, oh, I missed one because it relies on the contract that if enum == x then fieldA is set but if enum == y or z then fieldB is set but now when enum == q neither fieldA nor fieldB is set...” and I am happy that you haven't had these problems but yeah, some microservices are tightly coupled by this definition.
So what does it mean? You want the structure of your HTML to say the browser, that here is anything? Does your designer just give you a design with a huge blank rectangle in the middle? When does it ever make sense?
Isn’t it much more meaningful to create a <gallery> component (it can be done through a template-engine, react, whatever, that’s another point).
I mean, sure, we could just program with void pointers everywhere, but is it really a good idea?
I think CSS does a good job at separating structure from presentation. Or what do you think was the original intention? Do you think Tailwind fixed the core issue with CSS?
Your “semantic” CSS depends on your HTML structure. Tailwind’s HTML depends on a fixed set of CSS styles. None is any more coupled than the other, it’s just that the direction of the dependence is different.
Depending on what is expected to change more, both can be a valid approach: e.g. for a blog post that only uses heading, emphasis, link, styling it from CSS alone is definitely the best choice (that’s what it was developed for). But I would argue, most real-world web applications see more HTML-changes, and thus the other direction may make more sense.
Fully aware of this post. It's well written and I can see why people follow his stance. But I don't share Adam's history and experiences with semantic CSS. I genuinely want to separate structure from styling and not end up to this situation:
>Similarly, I haven't seen a pattern where good CSS authors do not like Tailwind.
well I haven't used Tailwind but from the examples I've seen I would hate it, and from what I can see I would hate it for the same reason that I hate all CSS abstractions I've worked with - because they limit what I can do with CSS in the interest of making it easier for other people who are not that good with CSS to get their work done OR it will require me to do things in a particular way when I believe I know a far better way of doing them.
Now I'm not arguing I am a good CSS author, I do lack some things (like a good feel for design imperfections so I don't notice when something is off) but I am saying I am generally familiar with this behavior in devs regarding other technologies, if one is sufficiently good with a technology one dislikes restraints placed on usage by some external library.
This is why people always comment "This would be so easy if we weren't using {X} technology on top of our code" because they generally are comfortable using the lower layer that the higher layer is now controlling and preventing them from doing their work in what they see as the best way to do it.
So, all that said, I would expect that if Good CSS authors were forced to use Tailwind they would not like it. The assertion to the contrary is extremely surprising and I just don't believe it without some extremely surprising proof to back it up.
> well I haven't used Tailwind but from the examples I've seen I would hate it, and from what I can see I would hate it for the same reason that I hate all CSS abstractions I've worked with - because they limit what I can do with CSS in the interest of making it easier for other people who are not that good with CSS to get their work done OR it will require me to do things in a particular way when I believe I know a far better way of doing them.
Tailwind explicitly doesn't do this. You can do pretty much everything you'd do with normal CSS. There are a couple of edge cases where you have to add configuration (e.g. if you want specific media queries), but Tailwind doesn't limit you in any way.
I also support the assertion that good CSS authors like Tailwind.
There are a few important limitations, Tailwind can't fully support logical properties for example because `mb-8` could mean margin bottom or block. Any use case that requires styling one element based on another gets a bit hairy too.
Both of these can be technically worked around. Config may be able to disable existing margin/padding classes and replace them with a custom set, and the groups feature helps especially for simple cases of referencing other elements.
In my experience though, as soon as config is meaningful modified or you elan on the more obscure features for groups, pseudo selectors, etc. the learning curve has just been moved from the dev who doesn't know CSS to the dev who doesn't know Tailwind.
that is also a thing, when I look at tailwind, having spent my time learning CSS why would I want to learn another syntax to help me do what I can already do, and probably do better.
I just moved our website from semantic to tailwind after I didn’t understand the semantic bits anymore.
Main problem was: the semantic css was elegant, but understanding it again after half a year of not editing the page took super long.
Tailwind is clear. The code looks uglier but I instantly know what’s going on. No hidden things. And no fear in editing a piece of html that it will break sth else.
I wrote a new part of the website in tailwind to see what all the fuzz is about. Then I decided to rewrite the rest of it.
It's a small website, but it's complex with desktop and mobile layouts, audio players etc etc. There's a design system behind it, but many small exceptions to it to make it look really nice. Writing the new part of the website was so fast with Tailwind that I just thought I'd give the rewrite a go. It wasn't that painful, and while it definitely took longer than understanding the existing bits just to edit sth, it also allowed me to clean up the CSS mess that had accumulated over two years.
If I had just sat down and tried to clean up the CSS mess, it probably would've taken longer if I hadn't converted it to Tailwind at the same time. And I needed to do some cleanup as we were trying to tie different sub pages into a coherent experience.
Just to clarify: please don't take this as advice of "go and rewrite your code". Rewrites are almost always bad. But in my case here, it was a good decision. My personal learning is that a startup landing page just changes too much to be able to fit into semantic classes nicely.
Indeed. This is a matter of taste. I personally find semantic CSS much clearer. I move faster with it because I need less code to achieve the same thing, and the resulting site is leaner.
Copy paste for styling isn't as bad as it sounds. Obviously with React you get components you can reuse. But manually chucking out mb-2, mx-2, my-3 to get it to look nice to the eye and having the same things repeat in different spots without a "meaning" or "semantics" can be quite nice. For small-medium size projects of course.
That quickly falls apart when I add an mb-2 to a Thingy, but fail to check that Thingy is also used inside SideThings (which we all forgot existed at all) wich already has whitespace (but somehow uses mt-2 because the developer preferred to declare intermediate whitespace at the top instead of the bottom).
So when the communication dept calls me on friday, I add quickly add an #main div.nth-child(2).mb-2 { margin-bottom: 0 !important } to overrides.css and call it a day.
Point being: manually sprinkling mb-2s throughout your codebase is a recipe for disaster: an sure way to an unmaintainable frontend.
Or worse, it breaks at a completely different site you didn’t even touch, won’t notice and might also pass through manual testing (if that’s even a thing at your place), and end up in prod. Sure, there are AI-based testing tools that check for frontend changes, but let’s be honest, they are very rarely used, if at all.
Hacks like this exist in non-tailwind projects where the classes have lost all meaning and the CSS is in a messy state.
On the other hand, in tailwind you could organize things well using your template/component system, be that ReactJS, Vue, ERB, EJS, CSHTML etc.
I agree there is subtlety though. A well organized and "benevolent dictator-ed" crop of CSS files is going to be more powerful than it's Tailwind equivalent due to having selectors etc. available.
Yep ! This is the huge problem with css and «meaningful» selectors, no matter how much you try to make things clear, nuances and details fades and you end up going back end forth between css code, inspector and HTML to figure out what's happening.
Utility based css selectors that do one and single thing are clear and explicit, removing most (but not all) that hassle.
I strongly doubt a reasonably designed "semantic" design can be half-forgotten after taking a break; the fine points of the implementation of sophisticated rules (e.g. why did we put the left border on the parent and the right border on the child) might suffer from obsolescence and lack of documentation, but the markup structure and the names should be obvious.
Forgetting what plethora of non-semantic classes should be used on what elements seems a far more likely problem.
Yes, there are conditions of people and project where you can avoid those.
But they a are plenty of situation when it becomes too hard, such as when you can't constraint a designer to your implementation details, or that same semantic concepts have different UI cases.
I don't understand why tailwind would force you to write more divs then CSS. How do selectors solve the problem of how many elements you need to use to achieve the same design?
Extra divs are wrappers that help you style the parent element to add flex layouts for example. Or why do you think the official Tailwind template has so many nested divs with utility classes?
For Tailwind all I ever do is copy big blobs of tailwind "styling" text (classnames) into my components from someone else's "tailwind components." Tailwind themselves even offer a component library.
I always thought semantic css was easier and made more sense, and when we were using Style Components we could still go ahead and have the styling right there in the javascript code if we wanted, best of both worlds, really.
But right now the whole industry loves tailwind so I use tailwind. So I use react, er, nextjs. So I use webpack. So I use yarn, or wait we're back on npm now right?
Thanks for confirming my suspicions regarding Tailwind. Their marketing loves talking about how small the CSS file size is vs semantic, but that code still has to go somewhere - and it's a bait and switch of "smaller css" with the cost of an inflated html file
* The irony being in this article is that the CSS file is also larger
tbf the examples brought up in the blog post omitted semantic attributes such as lang and the whole dom structure (all the divs!) and the other data- attributes just to make it look even worse. OP is severely opinionated.
The Tailwind markup vs Semantic markup example just straight sucks.
- There are useless empty elements in the tailwind example
- There are so many different breakpoints, paddings and margins in the depths of the tailwind DOM, that can not be achieved with the minified semantic markup as it is simply lacking the DOM structure necessary for it. Substituting just html > body > header > nav > a with html > body > div > div> header > div > div ... just isn't a correct comparison. It's like comparing airplanes to cars.
- There are data sttributes such as data-new-gr-c-s-check-loaded or data-headlessui-state. Also, totally unrelated to the semantic markup comparison.
This is just bloated asf.
Do you even know what semantic markup is? Maybe some people are just stubborn and try to find anything against tailwind because they just don't like it.
Bear in mind that the Tailwind example is the official template made by the Tailwind developers themselves. It is the prime example of Tailwind and it's benefits and best practises. (Should be at least)
I don't agree with this post at all. It seems like it's been written by someone who never actually used Tailwind CSS: I had the same doubts before using it.
Author here. Not that it matters, but I have certainly used Tailwind. I even reverse engineered the Spotlight code to semantic CSS. Fully aware of the ins and outs of the TW ecosystem.
Certainly not black and white. The article merely states that you need less HTML/CSS code to do the same thing and the resulting site is leaner and faster.
Arguably, this is the worst of both worlds. In a React world, you'd have a <Button variant="primary"> component for this.
By using @apply excessively, now you have to deal with class names and the resulting messy conflicts, and yet you still can't/don't write plain CSS and harness its full power.
Hm. @apply solves a problem. Basically you’re better off writing with the utility classes if you can extract your component out to… well, a component. If for whatever reason you can’t do that, @apply helps you avoid repeating yourself, which seems to be a reasonably good alternative to what’s in TFA.
But there are other (better) options available in most circumstances. Tailwind actually have a pretty good write up here:
That said, it’s easy to come up with a ‘gist’ like
> The semantic version is 8 × smaller, renders faster, and is easier to modify and extend.
but then underneath say you’re not minifying, and you’re not publishing the source code… it’s almost like the author had the conclusion before starting to experiment.
This is not a good comparison. The Neu implementation lacks a lot of styling and features from the Tailwind CSS implementation. It's not responsive, missing styles for a bunch of components, using much simpler styles for other components.
That example with a million div for the nav with tailwind is so ridiculous.
I agree with the conclusion but that first example is so grotesque that it made me discard the whole article
One other advantage of tailwind is that it makes it easier to work collaboratively with people of different css level and it requires less review, less possible side effect. But it's clear to me that clean semantic well developed css is way better than tailwind.
Author here: I'm sure there are a lot of corner cases since I did not test with all the browsers. My main focus was on writing, and explaining the differences between the two approaches. When/if Nue provides official templates they will obviously be fully tested. I expect the size of CSS grow by 1-5%. Certainly not 7x.
So given the popularity of tailwind I can conclude with confidence, that semantic movement has failed. HTML does not need semantics (because every big site is absolutely filled with divs) and CSS does not need semantics either. Spending time thinking about semantics is wasting time.
It will depend on what templating framework the websites are using. Server-side rendering frameworks can allow users to write semantic components -- e.g. `<menu-button>` -- that get expanded to HTML markup, which will often be realized as many divs.
This is often necessary to be able to properly style things like select controls or create more complex UI/markup.
Tailwind is useful in this case, or with things like react and vue where the layout can be specified once in the component templates. If hand-writing the classes, the semantic model makes sense as it allows you to remain consistent in what styles get applied.
CSS and HTML is great for documents - not so great for applications. Most sites end up implementing their own navigation UI/UX at a minimum (an application) - many end up as more applications.
Is "Semantic CSS" just... CSS? There's no such thing as "Semantic CSS". There is Semantic HTML. I suppose there is CSS for Semantic HTML.
I've read the article and can't figure out if this is comparing the CSS framework Semantic UI to Tailwind?
If we're comparing CSS to a CSS framework, it seems fairly obvious to me that it's going to be bigger in size, and slower to load, and that the amount of code required to be generic (rather than totally specific) would be larger too.
it's just CSS yes, with the focus on naming your classes depending on the content you style instead of their looks. "article-excerpt" instead of "bg-white padding-2 shadow rounded".
Writing and maintaining clear concise CSS for a large application requires serious effort. It usually fails fast as the design grows and changes and a larger team tries to work on the codebase.
This falls down to the old "it depends" slogan. Tailwind is a great way to scale a your css with a larger codebase / team while semantic CSS will give you a smaller tighter stylesheet and cleaner html for that single handcoded landingpage or blog.
Also the download cost of tailwind falls as the size of the app grows and each utilityclass is used in more contexts.
The article is interesting but really feels unfair sometimes, it doesn't help:
- The whole "amount of CSS" part is unfair when the Semantic CSS implementation isn't responsive at all, so of course it will be lighter, it does less.
- The part about the big number of HTML elements is a bit frustrating too; tailwind doesn't require you to use more HTML tags at all. It's totally possible to redo the Semantic CSS example with tailwind by not adding any HTML tags.
Besides that, it's still interesting to try and compare what is the best between big HTML (atomic, tailwind) vs big CSS (semantic).
Tailwind is not perfect, and sure, sometimes, you can get more performant code by writing it the semantic way. Sometimes.
But tailwind sure is a great way to easily write maintainable CSS in a team with different skill sets, producing really performant code by default, on large web apps.
- compare the tailwind example and yours, they don't have the same behavior. The tailwind one has a specific mobile menu for example. And that is just one example.
- sure, but since the tailwind page does more things, it's logical it has more code. Your base for comparison is production used code, sold to people. So of course it's polished, it must handle browser bugs and other things you might not expect in a quickly implemented alternative for a tech article. So I'm not surprised the code is bigger. That doesn't say at all that tailwind == more HTML tags :)
Tailwind example indeed does more, but only slightly. Would increase the size of CSS by 1-3%. The semantic version has clearly enough to prove the point the article attempts to make: significantly less code is needed and the resulting site is leaner & faster.
If you really want to compare code snippet sizes, you must offer the same functionality. Otherwise, the comparison is meaningless.
You did the exact same thing before when you compared the Headless UI combobox with your nue.js implementation. Offering fewer features will result in less code. Shocker.
Besides, I don't really care for these comparisons. If something is 2x longer code but more maintainable, it is 100% worth it. Just because something is short doesn't make it better.
Features that look easy to implement and are not a big deal can turn out to be massive problems and require a large amount of code to actually pull of. I would, in some cases, count responsiveness to be one of those things. Stuff like that may seem like 5% of the
functionality but actually makes up a large chunk of the code. This is why you must always have feature parity if you really want to compare code size.
I know what you are talking about. Projects tend to slow down at the end of it. But responsiveness is not one of those things, at least on this project. My honest guess is 3%, the menu being the biggest piece. It can also be lazily loaded so that it doesn't increase the amount of primary CSS. Just like the dark mode was implemented.
Having said that, I promise 100% feature parity on my next article. Thanks for the heads up!
Is comparing the markup of an html page with all the style in html attributes, to an html page which imports style from a CSS file (which is not displayed) a fair comparison?
This article makes some bold, false claims. What is true is that Tailwind will generally result in a bigger stylesheet over the wire than if you hand-write CSS. That's a well understood trade-off, made in exchange for several benefits.
But then the article suggests that every claimed benefit of Tailwind actually doesn't exist, and that Tailwind is in fact worse on all fronts. He seems genuinely to believe that there is no merit in Tailwind's huge success at all, that it must entirely be explained as arbitrary flocking behaviour. What idiots we must be, going to all this effort to write bulkier CSS, more slowly, with less flexibility! I have to guess that the author has not given it a fair trial (i.e. using it on a real project in earnest) before coming to his conclusions, nor had any good conversations with old-timer CSS experts who love CSS and love Tailwind. He's jumped to the conclusion it's a thing noobs like because they find CSS too hard.
I made a similar assumption at first, that Tailwind was just another horrible "CSS-in-JS" type thing, which I always felt were for front-end newbies who were so reluctant to just learn how CSS works that they'd be seduced by shiny packaging and use horrible JS contortions to 'shield' themselves from inheritence and cascading (which are actually useful, powerful aspects of CSS when understood).
But then I started to notice its hype cycle felt different, that even a few old school traditionalists seemed hooked on it, and I tried it out in earnest on a project. And I soon realised it's a very rare thing: a real innovation in front end, the kind that comes along every 5-10 years, up there with the component paradigm. It's not another CSS-in-JS thing at all. With Tailwind, you are writing CSS, in a very real sense. And thus your success with it is highly correlated by how well you can write CSS by hand – it is not an alternative to learning CSS; it is an extra, more advanced thing you should learn after learning how to do handwritten CSS well. What it brings is a beautifully designed build layer that justifies its own existence better than any other build layer I have seen 20 years of front end development. It gives you a massive net reduction in 'indirection', letting you style things right where they are. And it reimagines style 'reusability' in a way that (to be as succinct as I can) prevents your styles ossifying your DOM structure too early during the creative styling process. These are some of the reasons why Tailwind has blazed through the industry the way it has. Not because we don't understand CSS and want to avoid it, but because we want to write more of it, quicker and more flexibily.
You claim that you can only move faster with Tailwind when one of these is true: when you don't care about 'badly structured' CSS, or when you are completely new to CSS, or when you don't care about reusable modules.
I care very much about the structure of the CSS files I generate with Tailwind. I have been writing CSS for over 20 years. And I care very much about reusable modules – before good composable component systems came along, CSS classes were the best solution for this, but not any more imo. And I can certainly move faster with Tailwind in most situations.
Sorry for the negativity btw, I don't want to discourage people posting about their experiences with a tool. I just doubt you have really given Tailwind a fair trial. You're obviously a seasoned front ender who understands CSS deeply, but I don't believe you have understood Tailwind properly before coming to your conclusions about it.
> when you don't care about 'badly structured' CSS
Is a completely different claim than this:
> You are comparing Tailwind with your earlier, badly structured CSS
I'm sure we both care about CSS structure. My claim talks about developers' past experiences. If you enjoy semantic CSS, you probably won't switch to (unsemantic) Tailwind. And vice versa: if you never mastered semantic CSS (even hated it), you are more eager to switch and find the new way more enjoyable.
OK, I'm not sure how that affects my point. The point is, I'm living proof you are wrong on this particular claim. Tailwind makes me faster, and it's not for any of the reasons you said could be the only reasons.
The more general issue is your article doesn't demonstrate that you have grasped the purported benefits of Tailwind. You just deny they exist. It would be fine if you showed that you understand the purported benefits before explaining why you still prefer handwritten CSS on balance.
In particular, I can tell you don't understand that no longer using CSS classnames for reusability (and instead extracting components for reusability) is exactly what Tailwind users love about it, and no, not because they "haven't mastered semantic CSS". I know from experience that is initially unintuitive to a long-time CSS author, because I had a kind of Stockholm Syndrome for naming everything semantically and keeping my DOM super clean and avoiding extra divs. With Tailwind, you generally only name things when you extract them to components, and it's hard to explain but I after a while I came to realise this is just the perfect moment to name something, leading to better conceptual separation of semantics and styling logic, and much less proliferation of 'things' and ossification of the DOM.
I am not arguing Tailwind should be used for everything. I still use hand-written CSS for many things. And it is fine for someone to not like Tailwind after understanding it. I am just saying that you haven't understood it yet. Perhaps you wouldn't like it anyway, but imo it's worth really giving it a try.
By the way, giving it a try means on a real project. If I have a one-off, never-to-be-edited-again page design such as the example in your article, then I would probably use handwritten CSS with semantic classnames, as the exercise would be to make the cleanest, most elegant and fast-loading bundle of HTML and CSS possible. But in more realistic component-based projects that are expected to change over time, Tailwind can be an excellent trade-off, sacrificing a tiny (really, tiny) amount of elegance and bandwidth for a lot of engineering flexibility.
210 comments
[ 2.9 ms ] story [ 194 ms ] thread- Did you compare both versions of the page for feature parity across multiple browsers, devices, and breakpoints?
- what exactly accounted for so much bloat with the tailwind version of the site? Was it the CSS itself, or the classes that accounted for most of the difference? If it was the CSS, did you optimize it as per Tailwind's docs[1] so that unused styles weren't included in the final page styles?
- How much longer (if at all) did you take to design your selectors for the semantic version in order to reduce repetition, than you might have taken for creating the template with tailwind?
- How did the final sizes compare after brotli compression?
[1]: https://tailwindcss.com/docs/optimizing-for-production
2. I did not optimize the Tailwind version. The Tailwind developers did.
3. I have no idea. I only did the semantic version. It was quick, because I have done so much CSS in my life
4. You can check that out yourself. Both sites are brotli compressed
But tailwind/nextjs version has a lot of files incluced (ie. svg icons) which the other extracted to separate files.
Also, nextjs adds its own code that is completely unnecessary. Such as, 15 reponsive versions of the same image file, script tags at the end with the whole content in json.
This comparison does not feel objective (or honest) at all to me. If you want to prove to professionals that your CSS solution is better, you need to provide much stronger evidence. Preferably ones those professionals can't disprove within 30 seconds of comparing the examples themselves.
Can't wait to use Nue for my next project, currently wait for CSS implementation (even if it is not necessary for Nue). Project is currently in design phase, so i have luckily time.
I have a few questions:
- What exactly do you mean by "Semantic CSS"? I've never heard this terminology before (might just be OOTL). I get the parallel between this and Semantic HTML, but I guess it's not as clear what it is supposed to mean for a styling language to me. Is it just native CSS (or "pure" CSS)? At least, as a result, I have no idea what this means, or by what measures you decided what constitutes "semantics" in Tailwind's CSS:
- A small nitpick, maybe: could you somehow replace the top links to the two versions you compare with actual HTML links instead of JS events? This prevents middle-clicking and the location is replaced so it's tough to compare the two sites while reading your article. I have to click on your link, copy the url, and open that in a new tab. At least, opening the page in a new tab would be nice.<a className="group mb-8 flex h-10 w-10 items-center justify-center rounded-full bg-white shadow-md shadow-zinc-80
The links are now fixed. Thanks!
I have often used their components as a basis, and clean up huge chunks because I feel that is in my interest for future maintainability - it is not a fault of Tailwind, but a decision they made.
You can make a "semantic" design , using Tailwind where it will save you tiem or add readability, and get the best of both worlds.
Possible on a later bigger update (if necessary), a complete app would be rebuild. But here is the same problem: why should you do it, if your project works?
I personally had not seen such an update in early stage of a startup. Did somebody have insights or examples?
In fact, if there's enough interest, I'd probably build it too...
Tailwind is somewhat like lisp in the aspect that people usually don't get what all the hoopla is about. All they see is parenthesis and want a "lisp" without the parenthesis once they figure out polish notation
For a lot of applications (like a blog), what the author calls "semantic CSS" is the right thing to do. But there comes a point at which it is no longer feasible. We have a long history of SMACSS, object oriented CSS, BEM, CSS Modules, scoped CSS, and now Tailwind to make it work.
> Because mastering CSS requires practice. It takes several failed attempts before you get it.
Explaining the popularity of something by simply proclaiming "git gud" isn't a very strong point. For newer CSS devs, it does take away the woes of clashing naming and a badly structured cascade. The rest remains and the results remain shite. Similarly, I haven't seen a pattern where good CSS authors do not like Tailwind.
The fact is that updating css in a big project and a big team is very difficult. Rules are scoped globally. It only takes a junior making a few design mistakes and now you don't what you are going to break if you update anything.
With tailwind the css is fixed and will never change. So you just change your html and you know what to check/what to test again. For any medium to large project this is a big QA & time boon.
"just use code review, naming convention, xxx best practice". This argument is similar to "just don't make mistakes". Mistakes will be made. With semantic css you will suffer.
Is kinda what I said.
https://nuejs.org/blog/tailwind-vs-semantic-css/#best-practi...
But you don't explain why the tailwind version is tightly coupled and the semantic version is loosely coupled. And you don't do it because it is simply not the case. The coupling between html and css is not tighter or looser. It just goes in a different direction.
> The semantic version, allows you to change the design of the gallery freely. You name the component and style it externally. With Tailwind the style cannot be separated from the structure.
Same with tailwind. You update your component to update its style. With semantic you can update the css rules without touching the html, but you don't know if this css change won't break another part of your design. If you have a simple html structure like a blog with very few components, then semantic works (but so does anything really). If you have lots of components and you need to update them in order to add new features, then semantic will bring more issues.
What kind of fragile markup and problematic CSS global rules are you worrying about?
I’m wondering that too. If I’m writing a SPA, styles are usually scopped to the component with a base styles for UI atomic elements. And some conventions around spacing. If it’s a website, it will be separated in layouts and small components Bootstrap-like. An update will be identified as a variant or a special case.
As for coding this, search all files is usually a godsend when refactoring. You can use ripgrep if the editor does not have a good implementation.
You want to preserve the theme an consistency of the UI, but adapt its functionality. For this type of changes the semantic version is problematic.
You don't like global styles? Don't use global styles, period.
Even without adopting Nue and staying on React, CRA and Next comes out with CSS Modules out of the box. Angular and Vue have their own equivalent.
as for tailwild, I just can't bare its wide-ness - too many className attribute string going over 150~300 char widths, with some ternary operators mixed on top...
(seems vanilla-extract / ete have better DX, with occasional inlining for immediate deadlines)
But the problem is that you need to make sure that a given css change is going to affect only a specific set of component. So you need to check all components. Since it will be too time consuming, you will probably skip this step and hope for the best (and do some QA to check that nothing is obviously broken).
- read from webpack's output
- read from css-in-js: emotion / vanilla-extract / etc
https://nuejs.org/@spotlight/
vs here:
https://nuejs.org/@base/
(the gallery component)
If I modify the HTML structure, the component specification changes and CSS obviously needs to be updated.
Loose coupling vs tight coupling has an established meaning ≈ “if a change to module X causes a failure in module Y, then we say X and Y are tightly coupled.” This has some “knock-on” effects that you are trying to gesture at, like, “If two modules are tightly coupled then because of that tight coupling we usually have to coordinate deployments between them both.” And then you're like “I can ship new CSS without having to coordinate a deployment of HTML, that must be loose coupling then!”
But this is a sloppy way to use the language, because in the one case you have one module, and in the other case you have two: there is no coupling in the Tailwind case because there is nothing to be coupled because it all ships as one module, then you construct a more modular approach, one with two modules in fact, and these modules are (depending on how you squint at it given that we are doing decorative programming and there are no explicit errors) in fact “tightly coupled”—the semantic HTML essentially provides a customized API which the CSS uses by hooking into; this is a common form of tight coupling in microservice deployments for example.[1]
So you are actually looking at, one module plus a static asset that defines a common language, vs. two modules that are tightly coupled, and declaring that the two modules are “more loosely coupled” and this is somewhat of a nonsense thing to say. Like I get what you were going for, I didn't write this as a top level complaint because I understand what you meant, but here in the thicket of comments I see that you are not communicating well with your interlocutor because of this sloppiness and language, you might want to pivot?
[1] This point about microservices is likely to outrage a random passerby, look I am sure that your microservices use RPCs against HTTP APIs in a way that doesn't cause you much grief in having to update multiple modules in one commit (if monorepo) or stage “topics” (Gerrit’s term) where you carefully have to merge 2 or 3 pull requests to different repositories all at the same time (if multi-repo). But even though your microservices are loosely coupled, I have worked on very similar architectures where we did have to stage topics and if you didn't then changes to service X would break service Y, “We’ll ship a new value for this enum, that requires changing the common schema repo, which requires updating every microservice that looks at that enum so that it doesn't have a deserialization failure ‘this is not an allowed value for that enum’ when the new values start rolling out, the ones that actually switch() or if() on that enum need to be updated to do the right thing, oh, I missed one because it relies on the contract that if enum == x then fieldA is set but if enum == y or z then fieldB is set but now when enum == q neither fieldA nor fieldB is set...” and I am happy that you haven't had these problems but yeah, some microservices are tightly coupled by this definition.
Isn’t it much more meaningful to create a <gallery> component (it can be done through a template-engine, react, whatever, that’s another point).
I mean, sure, we could just program with void pointers everywhere, but is it really a good idea?
Isn't "CSS scoped to components" nowadays a basic feature of frameworks?
Your “semantic” CSS depends on your HTML structure. Tailwind’s HTML depends on a fixed set of CSS styles. None is any more coupled than the other, it’s just that the direction of the dependence is different.
Depending on what is expected to change more, both can be a valid approach: e.g. for a blog post that only uses heading, emphasis, link, styling it from CSS alone is definitely the best choice (that’s what it was developed for). But I would argue, most real-world web applications see more HTML-changes, and thus the other direction may make more sense.
https://nuejs.org/blog/tailwind-vs-semantic-css/img/markup-b...
well I haven't used Tailwind but from the examples I've seen I would hate it, and from what I can see I would hate it for the same reason that I hate all CSS abstractions I've worked with - because they limit what I can do with CSS in the interest of making it easier for other people who are not that good with CSS to get their work done OR it will require me to do things in a particular way when I believe I know a far better way of doing them.
Now I'm not arguing I am a good CSS author, I do lack some things (like a good feel for design imperfections so I don't notice when something is off) but I am saying I am generally familiar with this behavior in devs regarding other technologies, if one is sufficiently good with a technology one dislikes restraints placed on usage by some external library.
This is why people always comment "This would be so easy if we weren't using {X} technology on top of our code" because they generally are comfortable using the lower layer that the higher layer is now controlling and preventing them from doing their work in what they see as the best way to do it.
So, all that said, I would expect that if Good CSS authors were forced to use Tailwind they would not like it. The assertion to the contrary is extremely surprising and I just don't believe it without some extremely surprising proof to back it up.
Tailwind explicitly doesn't do this. You can do pretty much everything you'd do with normal CSS. There are a couple of edge cases where you have to add configuration (e.g. if you want specific media queries), but Tailwind doesn't limit you in any way.
I also support the assertion that good CSS authors like Tailwind.
Both of these can be technically worked around. Config may be able to disable existing margin/padding classes and replace them with a custom set, and the groups feature helps especially for simple cases of referencing other elements.
In my experience though, as soon as config is meaningful modified or you elan on the more obscure features for groups, pseudo selectors, etc. the learning curve has just been moved from the dev who doesn't know CSS to the dev who doesn't know Tailwind.
Main problem was: the semantic css was elegant, but understanding it again after half a year of not editing the page took super long.
Tailwind is clear. The code looks uglier but I instantly know what’s going on. No hidden things. And no fear in editing a piece of html that it will break sth else.
Huge upside.
Longer than converting the entire website to Tailwind?
It's a small website, but it's complex with desktop and mobile layouts, audio players etc etc. There's a design system behind it, but many small exceptions to it to make it look really nice. Writing the new part of the website was so fast with Tailwind that I just thought I'd give the rewrite a go. It wasn't that painful, and while it definitely took longer than understanding the existing bits just to edit sth, it also allowed me to clean up the CSS mess that had accumulated over two years.
If I had just sat down and tried to clean up the CSS mess, it probably would've taken longer if I hadn't converted it to Tailwind at the same time. And I needed to do some cleanup as we were trying to tie different sub pages into a coherent experience.
Just to clarify: please don't take this as advice of "go and rewrite your code". Rewrites are almost always bad. But in my case here, it was a good decision. My personal learning is that a startup landing page just changes too much to be able to fit into semantic classes nicely.
Point being: manually sprinkling mb-2s throughout your codebase is a recipe for disaster: an sure way to an unmaintainable frontend.
On the other hand, in tailwind you could organize things well using your template/component system, be that ReactJS, Vue, ERB, EJS, CSHTML etc.
I agree there is subtlety though. A well organized and "benevolent dictator-ed" crop of CSS files is going to be more powerful than it's Tailwind equivalent due to having selectors etc. available.
And then 100 times doing the same later... We all know how it ends up looking because we all did it.
Utility based css selectors that do one and single thing are clear and explicit, removing most (but not all) that hassle.
Forgetting what plethora of non-semantic classes should be used on what elements seems a far more likely problem.
But they a are plenty of situation when it becomes too hard, such as when you can't constraint a designer to your implementation details, or that same semantic concepts have different UI cases.
https://nuejs.org/@spotlight/
I always thought semantic css was easier and made more sense, and when we were using Style Components we could still go ahead and have the styling right there in the javascript code if we wanted, best of both worlds, really.
But right now the whole industry loves tailwind so I use tailwind. So I use react, er, nextjs. So I use webpack. So I use yarn, or wait we're back on npm now right?
* The irony being in this article is that the CSS file is also larger
- There are useless empty elements in the tailwind example - There are so many different breakpoints, paddings and margins in the depths of the tailwind DOM, that can not be achieved with the minified semantic markup as it is simply lacking the DOM structure necessary for it. Substituting just html > body > header > nav > a with html > body > div > div> header > div > div ... just isn't a correct comparison. It's like comparing airplanes to cars. - There are data sttributes such as data-new-gr-c-s-check-loaded or data-headlessui-state. Also, totally unrelated to the semantic markup comparison.
This is just bloated asf.
Do you even know what semantic markup is? Maybe some people are just stubborn and try to find anything against tailwind because they just don't like it.
Btw Bootstrapt is still there
EDIT: Semantic css is also bandwidth friendly for mobile connection.
.button-primary { @apply rounded-full focus:ring focus:ring-orange-500 ring-offset-4 outline-none px-6 py-3 etc...
and it gives you the best of both worlds. You refactor common css code into components and still have the amazing flexibility of utility classes.
By using @apply excessively, now you have to deal with class names and the resulting messy conflicts, and yet you still can't/don't write plain CSS and harness its full power.
@apply is one of Tailwind's worst features, imo.
> even then only if you’re not using a framework like React where a component would be a better choice.
But you can have the best of both worlds with apply:
But with reusable components or unified design, you enter the @apply hell. At this point i could not see benefits of tailwind.
But there are other (better) options available in most circumstances. Tailwind actually have a pretty good write up here:
https://tailwindcss.com/docs/reusing-styles
That said, it’s easy to come up with a ‘gist’ like
> The semantic version is 8 × smaller, renders faster, and is easier to modify and extend.
but then underneath say you’re not minifying, and you’re not publishing the source code… it’s almost like the author had the conclusion before starting to experiment.
I agree with the conclusion but that first example is so grotesque that it made me discard the whole article
One other advantage of tailwind is that it makes it easier to work collaboratively with people of different css level and it requires less review, less possible side effect. But it's clear to me that clean semantic well developed css is way better than tailwind.
It's the actual HTML code in the sites that are compared
or the person who developed that with tailwind just love having dozens of div for a simple header > nav > a
that person is someone from the Tailwind team. I would expect them to follow their own bst practises on the official template they sell online.
I can't scroll horizontally and only half the text is visible.
This is often necessary to be able to properly style things like select controls or create more complex UI/markup.
Tailwind is useful in this case, or with things like react and vue where the layout can be specified once in the component templates. If hand-writing the classes, the semantic model makes sense as it allows you to remain consistent in what styles get applied.
For an example of "documents", see eg:
https://edwardtufte.github.io/tufte-css/
Or
https://alistapart.com/article/building-books-with-css3/
I've read the article and can't figure out if this is comparing the CSS framework Semantic UI to Tailwind?
If we're comparing CSS to a CSS framework, it seems fairly obvious to me that it's going to be bigger in size, and slower to load, and that the amount of code required to be generic (rather than totally specific) would be larger too.
This falls down to the old "it depends" slogan. Tailwind is a great way to scale a your css with a larger codebase / team while semantic CSS will give you a smaller tighter stylesheet and cleaner html for that single handcoded landingpage or blog.
Also the download cost of tailwind falls as the size of the app grows and each utilityclass is used in more contexts.
- The whole "amount of CSS" part is unfair when the Semantic CSS implementation isn't responsive at all, so of course it will be lighter, it does less.
- The part about the big number of HTML elements is a bit frustrating too; tailwind doesn't require you to use more HTML tags at all. It's totally possible to redo the Semantic CSS example with tailwind by not adding any HTML tags.
Besides that, it's still interesting to try and compare what is the best between big HTML (atomic, tailwind) vs big CSS (semantic).
Tailwind is not perfect, and sure, sometimes, you can get more performant code by writing it the semantic way. Sometimes.
But tailwind sure is a great way to easily write maintainable CSS in a team with different skill sets, producing really performant code by default, on large web apps.
- How is it not responsive? I can easily fix.
- The Tailwind example is the official template made by the Tailwind developers themselves.
- sure, but since the tailwind page does more things, it's logical it has more code. Your base for comparison is production used code, sold to people. So of course it's polished, it must handle browser bugs and other things you might not expect in a quickly implemented alternative for a tech article. So I'm not surprised the code is bigger. That doesn't say at all that tailwind == more HTML tags :)
You did the exact same thing before when you compared the Headless UI combobox with your nue.js implementation. Offering fewer features will result in less code. Shocker.
Besides, I don't really care for these comparisons. If something is 2x longer code but more maintainable, it is 100% worth it. Just because something is short doesn't make it better.
Having said that, I promise 100% feature parity on my next article. Thanks for the heads up!
But then the article suggests that every claimed benefit of Tailwind actually doesn't exist, and that Tailwind is in fact worse on all fronts. He seems genuinely to believe that there is no merit in Tailwind's huge success at all, that it must entirely be explained as arbitrary flocking behaviour. What idiots we must be, going to all this effort to write bulkier CSS, more slowly, with less flexibility! I have to guess that the author has not given it a fair trial (i.e. using it on a real project in earnest) before coming to his conclusions, nor had any good conversations with old-timer CSS experts who love CSS and love Tailwind. He's jumped to the conclusion it's a thing noobs like because they find CSS too hard.
I made a similar assumption at first, that Tailwind was just another horrible "CSS-in-JS" type thing, which I always felt were for front-end newbies who were so reluctant to just learn how CSS works that they'd be seduced by shiny packaging and use horrible JS contortions to 'shield' themselves from inheritence and cascading (which are actually useful, powerful aspects of CSS when understood).
But then I started to notice its hype cycle felt different, that even a few old school traditionalists seemed hooked on it, and I tried it out in earnest on a project. And I soon realised it's a very rare thing: a real innovation in front end, the kind that comes along every 5-10 years, up there with the component paradigm. It's not another CSS-in-JS thing at all. With Tailwind, you are writing CSS, in a very real sense. And thus your success with it is highly correlated by how well you can write CSS by hand – it is not an alternative to learning CSS; it is an extra, more advanced thing you should learn after learning how to do handwritten CSS well. What it brings is a beautifully designed build layer that justifies its own existence better than any other build layer I have seen 20 years of front end development. It gives you a massive net reduction in 'indirection', letting you style things right where they are. And it reimagines style 'reusability' in a way that (to be as succinct as I can) prevents your styles ossifying your DOM structure too early during the creative styling process. These are some of the reasons why Tailwind has blazed through the industry the way it has. Not because we don't understand CSS and want to avoid it, but because we want to write more of it, quicker and more flexibily.
I care very much about the structure of the CSS files I generate with Tailwind. I have been writing CSS for over 20 years. And I care very much about reusable modules – before good composable component systems came along, CSS classes were the best solution for this, but not any more imo. And I can certainly move faster with Tailwind in most situations.
Sorry for the negativity btw, I don't want to discourage people posting about their experiences with a tool. I just doubt you have really given Tailwind a fair trial. You're obviously a seasoned front ender who understands CSS deeply, but I don't believe you have understood Tailwind properly before coming to your conclusions about it.
---- You can move faster with Tailwind. But only when:
1. You are comparing Tailwind with your earlier, badly structured CSS or you are completely new to CSS development.
2. You don’t care about building reusable modules for later use. That is: you are not naming things that repeat. ----
> when you don't care about 'badly structured' CSS
Is a completely different claim than this:
> You are comparing Tailwind with your earlier, badly structured CSS
I'm sure we both care about CSS structure. My claim talks about developers' past experiences. If you enjoy semantic CSS, you probably won't switch to (unsemantic) Tailwind. And vice versa: if you never mastered semantic CSS (even hated it), you are more eager to switch and find the new way more enjoyable.
The more general issue is your article doesn't demonstrate that you have grasped the purported benefits of Tailwind. You just deny they exist. It would be fine if you showed that you understand the purported benefits before explaining why you still prefer handwritten CSS on balance.
In particular, I can tell you don't understand that no longer using CSS classnames for reusability (and instead extracting components for reusability) is exactly what Tailwind users love about it, and no, not because they "haven't mastered semantic CSS". I know from experience that is initially unintuitive to a long-time CSS author, because I had a kind of Stockholm Syndrome for naming everything semantically and keeping my DOM super clean and avoiding extra divs. With Tailwind, you generally only name things when you extract them to components, and it's hard to explain but I after a while I came to realise this is just the perfect moment to name something, leading to better conceptual separation of semantics and styling logic, and much less proliferation of 'things' and ossification of the DOM.
I am not arguing Tailwind should be used for everything. I still use hand-written CSS for many things. And it is fine for someone to not like Tailwind after understanding it. I am just saying that you haven't understood it yet. Perhaps you wouldn't like it anyway, but imo it's worth really giving it a try.
By the way, giving it a try means on a real project. If I have a one-off, never-to-be-edited-again page design such as the example in your article, then I would probably use handwritten CSS with semantic classnames, as the exercise would be to make the cleanest, most elegant and fast-loading bundle of HTML and CSS possible. But in more realistic component-based projects that are expected to change over time, Tailwind can be an excellent trade-off, sacrificing a tiny (really, tiny) amount of elegance and bandwidth for a lot of engineering flexibility.