Hacker News new | past | comments | ask | show | jobs | submit login
Utility classes aren't the same as inline styles (2021) (frontstuff.io)
41 points by PaulHoule on Feb 25, 2024 | hide | past | favorite | 53 comments



Utility classes which require a build step add complexity and bloat to an already complex and bloated ecosystem. Personally, I am using Bootstrap (without Sass), but even if I wasn’t, I would rather write inline styles or a custom style.css file than have to deal with build systems and, in general, Node/NPM churn and dependency hell.

Full disclosure: I am biased toward Django, htmx, and Bootstrap, and avoiding the use of build steps completely. The DX has been amazing.


A build step is absolutely my issue with Tailwind. Tailwind tries to ship with every single style you need, as a result it needs a build step to purge unused CSS. On the other hand, Tachyons CSS (which came out before Tailwind) provides 20% of the CSS for 80% of the utility and asks you to write your own CSS to cover anything else you need. As a result, it doesn't need a build step. I think that's the way. Tailwind is overengineered for what it is, and it requires buy-in into the npm ecosystem. Tachyons is a standalone collection of CSS styles.


> it requires buy-in into the npm ecosystem

Actually, that’s not true anymore. There is the standalone tailwind-cli that can be used without npm.

https://tailwindcss.com/blog/standalone-cli


That’s even worse; now you need to introduce a specialized, non-standard tool into your development and deployment cycles.


Once you start using dedicated dev servers (like Vite) with the preprocessing happening live, the problem becomes much less of a problem.


Does it really matter how good your DX is if your stack limits you to making virtual magazines and fancy crud apps?


I’m not sure what you’re responding to regarding virtual magazines or fancy CRUD apps, but I’d guess that 99%+ of all web apps built are CRUD apps—fancy or otherwise.


> Does it really matter how good your DX is if your stack limits you to making virtual magazines and fancy crud apps?

I'm probably misunderstanding your comment and I think if you elaborate a little it might help.

My understanding is that very few sites (or apps) are NOT a fancy CRUD app.


By volume, crud apps are the vast majority. They're also one of the least engaging type of work IMO, particularly once you've done a few. Compare writing a crud app with making an immersive game, or an application like photoshop/davinci/etc. I don't know about you but I want to do interesting, deep work and if my stack is holding me back from that, that's a problem.


I’m working on a B2B app. Things get more interesting when you need background tasks, notifications, interaction with 3rd-party APIs, etc. Successful CRUD apps usually become more than “just” CRUD apps.

Also, using libraries like htmx is actually more suitable for the type of work you’re describing than modern SPA frameworks with all the bloat they bring along. Non-SPA libraries play well with other libraries, rather than take over the entire rendering process.

I’m old enough to remember how things were before SPAs; with libraries like jQuery the Web loaded much faster and was actually more interactive than it is with the currently dominant paradigm.


I'm not going to fault you for using boring tools to build a typical semi-interactive CRUD website, and I agree that a lot of people make SPAs that could have been multi page apps and provided the same or better user experience while being easier to develop.

That being said, having done all the job queues, API integrations and so forth you mention countless times, and having done graphics, AI/ML and symbolic stuff like compiler development, I can say confidently that at their best CRUD apps are still comparatively boring to write. I personally think it's a mistake to latch on and get religious with a stack that's going to leave you stranded on the isle of boring code.


I actually want learn to build every one of the non-CRUD programs you mentioned (I actually enrolled in a Compilers course once but had to drop out last-minute). I don’t __want__ to spend the rest of my life building CRUD apps exclusively, they’re just my forte, and bread and butter, in the present.

With that said, some things that seem boring to you may be interesting to others. And I don’t see how tech stack is relevant; if building CRUD-ish apps, I want the best possible DX. Of course, if/when I venture out of my comfort zone I will need to learn a new stack, but React and Vue become even less relevant for me at that point.


> Compare writing a crud app with making an immersive game, or an application like photoshop/davinci/etc.

I hear you, but I don't see how immersive games are limited without a CSS build step. Games in particular don't usually have a lot of CSS anyway.

As far as complex webapps go, I'm not thoroughly convinced that the dev team is limited by the lack of a CSS build step. Sure, it makes things slightly more tedious, but not that much more tedious, surely?


Why would it not?


Sure, Tailwind isn't inline styles. Fine. That doesn't make it good. And yes, most CSS is average or below average in implementation. Most software is too. That's just statistics. Tailwind is an attempt to prevent people from recognizing that, like software, CSS requires design and modeling.

It's perfectly possible to have a CSS system that is completely maintainable. It takes skill. That skill can be learned, but it cannot be learned if a person is tricked into relying on rapid-application-development tooling like Tailwind and not taking the time to understand the real problems with their CSS (which is very often special cause variation and/or not taking it seriously enough).

Tailwind doesn't solve anything, it just trades one bad thing (poorly written CSS) for another. I won't list the problems with Tailwind as that's been done plenty. I'm just pointing out the fundamental mistake here.

My experience: The first version of our app used React + Tailwind. When we started to ditch React and use server rendered HTML our React components and their embedded Tailwind were useless. When we wanted to share styles with our emails, they were useless. We had to start from scratch. We now have a plain CSS system (well, SASS) that is very well maintained, has only essential variation, and is used across 20+ web apps (that are all served on the same domain -- the user thinks they are one app).


> It's perfectly possible to have a CSS system that is completely maintainable. It takes skill

Yeah, and people wrote complex games in assembly. The difference is maintainability and if it can be done by more than one people. CSS doesn’t scale across teams/dependencies in its vanilla form in my experience, not at all. It is “spooky action at a distance” almost by definition, often with no way to even control/revert what some deep dependency does.


> CSS doesn’t scale across teams/dependencies in its vanilla form in my experience, not at all.

In your experience. It was my experience too, until it wasn't. See this: https://news.ycombinator.com/item?id=39501442

> often with no way to even control/revert what some deep dependency does

Without looking at specifics, it is hard to say, but it is very likely that this is a design mistake. It's very likely that the person making these mistakes is also making these mistakes in code and creating tangled messes there.

The state of development is quite poor. "Average" (read: most) developers create tangled messes because it's all they know how to do and they've never taken the time to learn how not to do it. So, when something like Tailwind comes along and says "you can't make a mess with this", then of course they will jump on it. They will not, however, be very interested in recognizing the costs that come along with it. They also will be incapable of seeing that the other path is not only simpler, but it can become easier with practice.


I'm a fan of understanding the grittier side of the box-model but I've found that I can't outpace a system designed by a team of engineers to solve the problem of quick and easy layouts and components.

The comment above is definitely a great counter argument to rolling your own CSS from the ground up. I've seen projects with many engineers breaking each other's features because they weren't specific enough with their CSS targeting. The worst part is that sometimes it takes a keen eye to see that something broke in the first place - for example a font is wrong or a div moved etc. It's surprising how many customers don't even raise an issue, they just work around it.

I remember the days when the industry was shifting between Underscore, jQuery, Backbone, Knockout, Ember and React.

React obviously won with its bastardisation of compiled frontends and DOM diffing and Vue recently seems to have caused a stir by giving old paradigms a fresh coat of paint.

After all these years this ecosystem is still in flux...reflux...redux... and no static solution is going to suit every individual or survive indefinitely - the ideal solution seems to be able to adapt to requirements as they come and embrace stack requirements as learning exercises in what does and doesn't work.


Or it's to call to the carpet the people that are enforcing "stack requirements" of tooling that is overly complex for the job.

> I've seen projects with many engineers breaking each other's features because they weren't specific enough with their CSS targeting.

Agreed, and to add to that -- unless a team is willing to stop the line and understand why and how this happened, it will keep happening. Tailwind eliminates classes of problems without requiring people to collaborate or think. That's its "benefit". As I've said elsewhere, it comes with costs, but many of those are subtle (many devs won't have enough experience to observe them) in their short term effects.

> I'm a fan of understanding the grittier side of the box-model but I've found that I can't outpace a system designed by a team of engineers to solve the problem of quick and easy layouts and components.

> I've seen projects with many engineers breaking each other's features...

I don't usually comment on people using "engineer" to describe software developers, but the irony in these statements is a bit much. What is being described is not engineering, it is hacking. It shouldn't be a surprise that when we hack, we break things.


> Inline styles only apply to the elements they’re declared on

This is a feature. Utility classes that do more than 1-2 things are hellish to reason about at scale, especially when they start interacting with each other


No, they're much worse. Lament for the folks that have to maintain that stuff for years after it saved you 30 seconds of dev time.


Regardless of how easy devs find it to be now, there's no denying that utility-only CSS is fighting the platform it's built on. jQuery and SASS showed the way for their specs to build toward. Contrast that to utility-only CSS, which I suspect is just going to get left behind as CSS solves the problems Tailwind, et al. try to work around.


On the contrary, utility classes actually drive you to HTML components/composing HTML fragments for HTML reuse, thus eliminating duplicate markup. This is a big problem that CSS, SASS, etc. are not solving and probably can't solve.


Instead of letting our tools lead us by the nose, what if we let design principles lead us? Why would a styling tool lead me to model my markup? That's the complete inverse of how one should be thinking about it. You model the markup first, you create reusable bits if you need them first. Then you style them. If the CSS forces your hand to change the markup, then you must, but that's becoming more and more rare with modern CSS.

I have no doubt that people that don't know the fundamentals at play have an easier time with Tailwind. That's how it goes with hacks. They're usually easier to apply than proper design and implementation. We're throwing in the towel with Tailwind, plain and simple.


> You model the markup first, you create reusable bits if you need them first.

Agreed, and with Tailwind you never have to leave this fragment of markup you're designing. You don't have to switch files to add CSS and invent meaningful names for styles, then ensure you use the same name in the markup file, nor do you ever have to hunt down and change these names and the markup across multiple files to ensure they are applied consistently down the road of you're using HTML components.

In fact, you rarely have to write CSS at all, you just focus on the markup and sprinkle in the predefined styles that you need. Much simpler.

> We're throwing in the towel with Tailwind, plain and simple.

If by throwing in the towel, you mean we're acknowledging that they took a suboptimal design path, I agree. Transclusion of HTML fragments would have been a much better path to take, and some people were arguing for it even back then (Xanadu folks IIRC). Now we've come full circle, and people are still arguing that decomposition for reuse are not good ways of structuring dynamically changing and evolving content.


For what it's worth, I was exactly where you are 3 years ago. I made all of the same arguments. I saw the value in Tailwind. I had come from rebuilding a CSS system for another application multiple times because we never could get it to work for us.

I would have never believed me now either, not until I saw it done first hand and learned how to do it. So, call it uncommon knowledge, or a rare skill, or whatever you want, but do know that not only is it possible, but the skills required are the same skills that enable you to build maintainable applications -- not just CSS.

In large part, this is why we failed in the past. We would often treat CSS as a second-class citizen and put our most junior "front end" developers on it. It should have been no surprise that it ended up as a tangled mess.

> You don't have to switch files to add CSS and invent meaningful names for styles, then ensure you use the same name in the markup file, nor do you ever have to hunt down and change these names and the markup across multiple files to ensure they are applied consistently down the road of you're using HTML components.

This isn't a real problem. The only time we've struggled to come up with a name is when CSS forces us to use some mechanical name that includes the word "container" or something like that. Usually, we just name the thing what it is. Usually, very often this is in the domain of the UI (hint, it is likely the same name as your React component, if you are using React and naming things well).

There's another problem here that complicates things. Most people shouldn't be using React for their forms over data applications. It's unnecessarily complex, but people now tend to think that server rendered HTML is yet another lost art. React plays very well with Tailwind in a sense, because you are already forced to make components for your UI, so what's sprinkling a little class soup in that file? What's sprinkling a few type declarations in the same file? What's sprinkling a few hooks in that file? What it all adds up to, once you can see it, is very difficult to read and reason about code all in one place. You can learn to see through the forest, but I personally would rather work on one tree at a time.

By the way, I'm not advocating for writing HTML in multiple places (and therefore re-using the CSS classes), that often should be abstracted in some way. In Rails, we use helpers and view template partials. Every technology has its options. Technically, web components fit this bill as well, but we don't make heavy use of them as I've found them unnecessarily complex.


I know we're just going to have to agree to disagree, but I find utility class based projects easier to maintain. Coming back to -- or inheriting -- css soup is never fun.


I think people compare the best possible CSS with Tailwind rather than comparing what actually gets done with actual Tailwind.


> I think people compare the best possible CSS with Tailwind rather than comparing what actually gets done with actual Tailwind.

Yep. The one Tailwind guy on the team will set everything up perfectly, and then no one else will ever have any desire to ever learn Tailwind, and it descends into a complete mess.

You can say "just learn Tailwind!", and see how far that gets you with a group of backend devs who've inherited some UI and just need to add a button.


I find this criticism interesting. Among the people actually doing it, it seems to be a near universal experience that Tailwind projects are easier to maintain than class based CSS.

What is it about Tailwind that is unmaintainable, in your view?


Utility classes give you the immediacy and power of inline styles in a consistent and controllable way, and they're a natural bridge to class based styling. If you've had a bad experience with utility classes, that's because you've had to deal with bad codebases that use them, rather than due to inherent issues.


Sounds a lot like the classic "Hate Agile? You just haven't done it properly" rhetoric


Maybe, if the normal "agile" definition was limited to just stories and a sprint structure of some kind - atomic user stories and short, feature centric development cycles are hard to argue against. If your intention is to liken Tailwind to SuperMegaScrumKanbanJujitsuMarketingCoderStyle agile, I can assure that there is no real similarity.


My comment has more to do with the No True Scotsman fallacy than it does with Agile


I've worked with Tailwind in 3 separate, huge projects, and it's infinitely easier to upkeep than the monstrosity that large SCSS/CSS codebases turn into.

There's no hunting down random css classes all over the codebase, no dealing with insane cascade issues, no idiotic specificity issues, no unused classes that everyone is afraid to touch because it might break some random div somewhere...


You can still write component-based CSS/SCSS without Tailwind and that solves all problems you mentioned


CSS/SCSS modules are still, for me, the best option to balance maintainability and flexibility within a component


Have you ever tried making your SCSS/CSS codebases not a huge monstrosity? It can be done.


This article is a strawman. No-one who has compared utility classes to inline styles is asserting that they are the same on a technical level, because that is obvious. The argument that utility classes are basically inline styles refers to how both assign style directly to a single element in a way that is not re-usable at a level of abstraction that promotes DRY. If your counter to this is "well those styles are contained in a React/whatever component which makes it DRY", you're making a different argument where the suitability of utility class frameworks relies on coupling with a component framework.

To take this article seriously I would need to see a section on the maintenance obligations imposed by utlity classes and inline styles, versus an approach such as semantic CSS. Until then it just looks like "well ackshually they aren't the same". With some strange lofty bits about knowledge.


> If your counter to this is "well those styles are contained in a React/whatever component which makes it DRY", you're making a different argument where the suitability of utility class frameworks relies on coupling with a component framework.

It does not require any such framework, although those frameworks can use the same approach. PHP and Perl were composing DRY HTML fragments even before JavaScript or CSS were invented. That this is possible and straightforward does not require more than the 30+ years of evidence that already exists.

But if you want more, here's a simple one: CSS promotes DRY of CSS markup at the cost of repeating HTML markup. Do you have more HTML or CSS in any given project?


It's not about DRY. It's about modeling. It's about design. It's about being able to look at markup (as a human) and tell what the heck it is. Tailwind soup obscures what things are. How many of your Tailwind-using apps ALSO have meaningful class names and IDs in your markup? How many of you rely on data-test-id or other such hacks to make up for the fact that you are not doing design.

Classes are used to categorize the markup. It's a document model. Model the document. Then, you apply styles to that. It's about design.


> It's a document model. Model the document.

I think this may be the source of many religious wars in frontend land. HTML was originally a document model, but now it does dual duty as a way to block off rectangular sections of application UI.

The purist approach of writing perfectly semantic HTML and placing all presentation in optional, layered style sheets works beautifully - for documents. But I don’t think it works as well when you are designing an interactive UI and you’re really just trying to manipulate pixels on a screen.

Tailwind sure is ugly, but it’s quick to write and a cinch to debug. And in my experience, most UI code is pretty ugly. It seems to come with the territory?


I've also made this argument, but I was wrong. It turns out it's also possible to model a UI. And, it's possible to do that relatively well with HTML. You can see web components or React for contemporary examples of doing this (though no guarantee that the example you look at will be doing it well).

It stands to reason that if it can't be done with HTML, then it can't be done with React, or Swift UI, or anything and we are all doomed. Of course, that's not true.

> Tailwind sure is ugly, but it’s quick to write and a cinch to debug. And in my experience, most UI code is pretty ugly. It seems to come with the territory?

UI code doesn't need to be ugly. It may come with the territory for most teams, but it doesn't need to be that way. We don't need to be working in giant monoliths that take an hour to deploy and run tests either, yet many teams are. We don't need to live in squalor, but when it's all we've known, we become accustomed to it. We give TED talks telling people how great it is to live in squalor and about the "one weird trick" that makes it a little more palatable.

The only "trick" we need is design. I'm not saying it's easy (in that it is readily available and applicable knowledge for the average developer), but it is attainable.


Don't you find when developing web apps that you often need to use elements that have no semantic value? I'm referring to divs that are needed as hooks/exploits/workarounds for CSS, such as redundant containers that have (or don't have) relative position, or are needed for their stacking context, or to contain overscroll, or to prevent a shadow from being clipped, etc.? These problems also grow more complex when you have to compensate for Safari's issues. I always try to abuse ::before and ::after to streamline the markup, but there's a lot these pseudo-elements can't do.


I wouldn't say it's often anymore, but it happens. It comes with the territory. We can sometimes avoid giving them class names by targeting them with things like immediate child selectors. We do end up with mechanical class names from time to time. But that doesn't mean we throw in the towel on modeling entirely.


I concur. A lot of frontend problems wouldn't exist if HTML had something like XSLT. The fact there's no non-javascript way to reuse HTML code is a already a failure of DRY. Pretty much every web 2.0 technique so far has been just us trying to add DRY to a language that doesn't support it.


> How many of your Tailwind-using apps ALSO have meaningful class names and IDs in your markup?

The markup doesn't need meaningful class names because CSS is encapsulated in reusable HTML fragments that have meaningful names. Your approach to structuring markup is simply unnecessarily more repetitive.

Poorly defined "design" principles have a cost, and where they conflict with concrete principles with a proven track record, like "reusable abstractions", then the poorly defined principles are wrong.


> The markup doesn't need meaningful class names because CSS is encapsulated in reusable HTML fragments that have meaningful names.

They have meaningful names... where? In React (or whatever). They don't in the web inspector (so just use the React dev tools! Yes, more complexity). They don't for testing tools like playwright, cypress, or capybara (so just use data-test-id! Yes, now you have to name something FOR TESTING PURPOSES ONLY, which is a clear sign of failure).

> Poorly defined "design" principles have a cost, and where they conflict with concrete principles with a proven track record, like "reusable abstractions", then the poorly defined principles are wrong.

I'm not sure that attempting to redefine design into something that you can ignore will do you much good, in the long run. If you think that "reusable abstractions" are a problem, then it's highly unlikely that there is a fraction of a square inch of common ground between us and any further conversation will just lead to frustration.


> They have meaningful names... where? In React (or whatever). They don't in the web inspector (so just use the React dev tools! Yes, more complexity).

They have meaningful names in the locations where people actually need to care about meaningful names: in the template language or the component model used to generate the final markup. If you're suggesting that class names should be meaningful for people who want to read the generated markup and they can only make sense of it because of class names, you're penalizing all front-end development for an absurdly small niche.

If you need identifiable names for testing purposes, that's what other queryable HTML attributes are for (id and name in particular). In your template language you already have the component name available, so just assign a queryable attribute using that where necessary. Nothing wrong with that.


It depends. OP's utility classes look too much like inline styles, with colons in the class names and whatnot. I can easily imagine a new developer seeing that, scratching their head, and wondering "wait, I can write CSS inside the `class` attribute?"

Tailwind is much more restrained in comparison.


utility classes are like coding in assembly language. there's often a 1:1 mapping to machine code (actual css), and almost always just too little abstraction for my taste.


> utility classes won’t let you deviate because they’re limited

Tailwind's just in time feature does negate this a bit. I'm a huge fan of Tailwind but I've largely avoided JIT for fear of losing the structure finite classes give me.

Edit: the down votes are interesting as this is literally what Tailwind JIT allows. Their post on it even says "We’ll likely add some form of “strict mode” in the future for power-hungry team leads who don’t trust their colleagues to use this feature responsibly."


[flagged]


The guys at OpenAI decided to use tailwind to style the boxes in ChatGPT




Consider applying for YC's Fall 2025 batch! Applications are open till Aug 4

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: