DEV Community

Nashe Omirro
Nashe Omirro

Posted on • Edited on

Thoughts on utility-first approaches in CSS

With Tailwind reaching 6,000,000+ weekly downloads on NPM, as well as becoming my personal go-to for hobby projects, I wanted to understand why it got so popular... And I believe the one primary reason is an issue within CSS that bothered developers for so long:

Class naming is "hard"

Is that it? Well, yes but it goes further than that. So let's first ask why class naming is hard.

Look at these two pictures, they are both doors.

A side by side comparison of two different doors

If we were to name this doors just like how we do in CSS, Let's ignore best practices for a second and call the first door something like yellow_door and the other brown_door.

Simple right? Now what if we had a slightly different looking door that's also brown, what then?

There are two ways we've come up with to solve this, renaming the doors to be more descriptive or using some form of modifiers:

  • normal_brown_door and modern_brown_door
  • brown_door and brown_door --modern

Okay.. Then what if we add 10 other doors with various sizes, colors, the amount of inner squares they have, the design they follow, oh wow!

What a headache... Well technically we can create some sort of structure that will name all the doors, but I'm too lazy to do that.

Class naming isn't hard per se, what's hard is avoiding the consequences of poorly named classes.

"Should I use modifiers for this? Should I create a base class? Should this class "extend" from the base class even when it shouldn't use some of the base class's styles? Should I instead refactor the base class into two classes or move some of the styles of the base class to one that inherits from it? Should I just copy the styles into a new class, do I need it connected to the base class in the first place?"

Questions like these attempt to make our CSS more maintainable in the long run, and solving said questions is a pain.

The natural way we "name" things

Okay then, since these doors are real, how do we describe them in real life? Simple! We describe them the same way: yellow door, brown door. If we had more doors, we'd get more descriptive.

A person calling someone at his desk

Picture this, you are on a call with a friend that's right outside and they're looking for the house that you live in. How would you direct them to the building?

The natural way we'd do it is to describe the building no? It's a two-story house, it's egg white, it has a red roof, the door is black and modern looking, etc. The more descriptions you add, the more better they could find the place. For many people, describing the house is the first thing that comes to mind. Yes, guiding them to the house is arguably more common but guess how we guide them? we describe landmarks and things that they see visually.

Getting to the point

Now imagine describing your house in succinct one-liners, it becomes harder doesn't it? Especially if there are other identical houses near you.

That's what we're doing with class names, we are trying to put descriptions of things and compressing them into shorter names and trying to make logical structures out of them. Wouldn't it be more easier if we just use the descriptions themselves?

Developers are built different

Developers focused on programming

As web developers, we understand CSS properties like how we would understand visual descriptions in real life, and that's exactly what a utility-first approach enables us to do in code.

A way for developers to visually describe an element instead of naming and creating OOP-like naming conventions for them. No need to name things that are just too abstract.

Components are easier to logic about

That's nice and all but we still have to "name" things, we have to name our components, and we still need to group styles into it.

That's true, even with a utility-based approach we can't avoid that, but we still drastically avoid naming abstract objects and we let our components handle the structure instead (which is somehow easier). When we create components, there is some logical sense behind it, whether it's because it has some functionality attached or maybe it's an easily-named object.

Now at this point, free reign is given to the developer with how they create their component libraries/systems, but it isn't cluttered by impossible-to-name objects and they only have to worry about their components as a whole. That div that has flex and auto-margin left in the Navbar component doesn't need a name, just described, and that Button's styles could be modified and overridden with props instead of using classes.

TLDR

Correctly naming things are hard, especially one's that are abstract in nature, describing them is easier, that's what a utility-first approach lets us do.

Drawbacks

The only implementation of this approach is none other than what first introduced it, Tailwind. Now it's really nice to not worry about naming... But then you end up with a messy hot pile of utility soup, which I and many other developers would agree is not ideal.

Some accept it as a tradeoff, while others think that it's too much of a con for what it offers. I personally feel that it should do better with it's @apply directive, making it easier to hook in to tailwind's internals but it's currently frowned upon to even use it, which feels like Tailwind leaning more towards a "utility-only" approach rather than what it claims.

Top comments (23)

Collapse
 
link2twenty profile image
Andrew Bone

Some Devs seem to love CSS (like me) and some Devs seem to not be such big fans. I think making CSS easier for people that struggle with it can only be a good thing.

I generally use BEM for class names and am looking forward to functions and mixins to make that code look and read easier.

It's an exciting time to be a web developer for so many reasons.

Collapse
 
nasheomirro profile image
Nashe Omirro

This begs me to question if CSS would've been better off if it was a programming language that was specifically for creating CSS rules, or at least had programming features from the get-go. Apart from the advancements that SASS made (CSS Nesting, includes, etc.), I have a feeling that it tries to emulate a lot of logical stuff like functions, if statements, variables, variable manipulation, logging variables, and so on...

I realized this when I tried vanilla-extract, a build-time CSS-in-JS library, the flow of it felt really familiar to SASS (apart from the arguably ugly-looking CSS objects), that could do everything SASS can do and more since it's with an actual programming language.

Collapse
 
ben profile image
Ben Halpern

Tailwind is when I stopped hating CSS.

Its tradeoffs are tradeoffs, but I feel so empowered when I know I can change markup right as I'm looking at it and just know that class is available.

Collapse
 
karsten_biedermann profile image
Karsten Biedermann

That is interesting. The idea of ​​Tailwind is great, but not for everyone. But that's so great these days, you have the choice.

Collapse
 
brense profile image
Rense Bakker

Tailwind is also when you stopped using CSS :p

Collapse
 
ravavyr profile image
Ravavyr

"class naming is hard"?
Like, are you serious? That is literally the easiest part of creating components.
Ok, so maybe "door yellow" and "door brown" is too hard to think of, when maybe someone will want "door blue not-round" or something.
You add a class, it's not that hard, at all.
The actual CSS writing is the tricky bit.

Tailwind and every other css framework's biggest problem is their naming conventions, it's like you have to learn a new syntax for each one instead of just using one class name per unique object and be done with it. oh and the horrifying markup people create with them with divitis.

All of them are great for cookie-cutter sites, and most sites are just that. Website creativity went away once responsiveness became important due to mobile phones and css frameworks became prevalent because some backenders didn't want to write CSS in the first place.

Don't give me this "class naming is hard" nonsense, come on now.

Collapse
 
nasheomirro profile image
Nashe Omirro

Thanks for the feedback, I thought things through a second time because of your comment, and I feel like I was able to form a better opinion, as well as notice that I tunneled into the idea too strongly and have edited the post accordingly.

Idk about website creativity and all that, I just got here lol

Collapse
 
aayyusshh_69 profile image
Aayush Pokharel

Agreed! 👍

Collapse
 
merri profile image
Vesa Piittinen

You might find this interesting: nuejs.org/blog/tailwind-misinforma...

Collapse
 
nasheomirro profile image
Nashe Omirro

Thanks for the link, I did find that very interesting. Before drafting the initial post I had thoughts on Tailwind's drawbacks.. primarily that it gets messy very quickly, even thinking that it should have a better way to use @apply directives! I didn't think it through much though hence I removed it.

I still feel like a utility-first approach could be done well, I understand that Tailwind was pushed/marketed very heavily but it still provided something that developers felt was good enough to switch to such a radical approach, even if arguably they have went in a different direction that what I would like them to.

I did notice my post shills too much for the approach though, will update it shortly.

Collapse
 
rganga1 profile image
Rakesh G • Edited

All the experienced(10+) feel including tailwindcss makes the code messier and unreadable. Structure(HTML) and styling (CSS) shouldn't mix up. They learnt the hardway of inventing the names for each class.
But new developers don't have to go through the pain. Tailwindcss speeds up our development no doubt about it. So headless css frameworks are headlessui.com on rage.
There are many amazing tools which support it - like clsx which helps us to group tailwindclasses, sorter and fold to make them readable.

Collapse
 
nasheomirro profile image
Nashe Omirro

Yeah, most job posts I've seen that target juniors mention Tailwind, and a lot of portfolio projects I've seen also use Tailwind.

I'm a little scared that a lot of trendy frameworks place so much abstraction to things that are arguably difficult but doable, it's getting more and more easier for developers to grab a package than create things themselves, which isn't bad, just that newer devs get better by doing those difficult things, but now the instinctual reaction to a problem is to pick up a package.

Who knows, I myself am relatively new, I and my generation of devs might realize that Tailwind's drawbacks are too much, and there would be a rennaisance of traditional methods.

Collapse
 
efpage profile image
Eckehard • Edited

There is a reason beyond class naming, that is based in the history of CSS. CSS was attached to HTML but they tried to be backwards compatible, so some of the initial misconceptions remained preserved. One of the misconceptions was the global scoping of any ID´s, regardless if you name a CSS rule or an element, all IDs are part of the same namespace. This is pretty ok for a simple page, but is very likely to cause naming conflicts on larger applications and any kind of toolbox.

Today we would need a much more advanced concept of scopes and namespaces in CSS, that is not based on global ID´s. Assume you could inherit CSS rules together with a class hierarchy. Any element derived from a class could inherit the class rules. It would absolutely make no trouble do use the same names in different classes.

I know, this is a fiction only, but that was the way this kind of conflicts have been solved in OO-languages in the past. ID´s had been valid in a class context only, so you knew which rule you had to apply by the class, an element was derived from.

Svelte tries to use a similar approach, but I suppose it is still a bit limited. But is eases the pain a bit, as it uses something like a module global namespace.

There are more misconceptions of CSS, but I suppose, a lack of precise scoping is the most important one.

Collapse
 
latobibor profile image
András Tóth

This is a very valid and painful point. Your idea though, it's not a fiction anymore! See CSS layers:

This is your complete guide to CSS cascade layers, a CSS feature that allows us to define explicit contained layers of specificity, so that we have full control over which styles take priority in a project without relying on specificity hacks or !important.

css-tricks.com/css-cascade-layers/

Collapse
 
efpage profile image
Eckehard

Thank you for the hint. Interesting to see if this solves the problem. I will definitively check it out.

This would be most interesting if you could create UI-elements that implicitely belong to a specific layer. I´m thinking of WC-lib´s like shoelace

Thread Thread
 
latobibor profile image
András Tóth • Edited

That is kinda the idea. To separate rules of the framework from your application.
This is a new layer of specificity rules.

There is also @scope to help localize changes. A lot of good has happened in CSS. It is time to write an article about "what to learn in CSS in 2024".

Collapse
 
syeo66 profile image
Red Ochsenbein (he/him)

So, this confirms: skill issues lead to things like tailwind.

Collapse
 
latobibor profile image
András Tóth

I see the problem coming from many sources:

  • lack of a popular, clear CSS learning path; it does exist though, but finding it is extremely hard
  • without the former, developers are afraid to spend time on learning scalable, CSS architecture, leading to wish to just intuitively f- around
  • bad frontend and especially full-stack traditions: developers don't think working on CSS is valuable, they see layout, not smart little layout-machines that are capable of describing many different sizes
  • unrealistic expectations from product managers: to make a page/app truly responsive, you have to work together with designers, you have to create custom solutions in many cases and define points in which your UI has to sacrifice, or reorder itself; it is much easier to think in "should look great in mobile" which is an extremely varied field of different screen sizes and expect the poor developer to just finish the UI in half a day.
  • failure to see spaghetti being built: Tailwind by definition is not DRY; you repeat a lot of your code. Now you can write DRY CSS, but only if you have spent considerable time understanding CSS archicteture. Therefore with Tailwind you will have repeated code, which has a super high resistance to change. A redesign that could have been 3 CSS files are now going to be 500 JS files. People are going to shout at you for changing their files they are working for their feature in another branch. Rebase/merge errors will happen and so on. Fun times. But we had utility classes.

So, the tradeoff to naming will be a tradeoff of inherently larger bundle sizes, worse performance and worst of all redesign is going to be extremely challenging whack-a-mole work.

Collapse
 
magicmn profile image
magicmn

In a component based framework (so basically any modern frontend framework) creating anything more than utility classes is just pointless. If I need the same style again, that usually means I just want to reuse the component. And if I just quickly want to lookup a style definition for a component Button className="primary" is telling me less than Button className="bg-blue p-3 text-white". If I need to describe more complex logic it's just easier to define a strings with utility classes for each case than putting everything in different classes in another file.

Collapse
 
latobibor profile image
András Tóth • Edited

Well I have a point for you here. Let's say you have a list of the same component 500 times. Now every one of them has a Catalyst button with this beautiful construct on them: className="relative isolate inline-flex items-center justify-center gap-x-2 rounded-lg border text-base/6 font-semibold px-[calc(theme(spacing[3.5])-1px)] py-[calc(theme(spacing[2.5])-1px)] sm:px-[calc(theme(spacing.3)-1px)] sm:py-[calc(theme(spacing[1.5])-1px)] sm:text-sm/6 focus:outline-none data-[focus]:outline data-[focus]:outline-2 data-[focus]:outline-offset-2 data-[focus]:outline-blue-500 data-[disabled]:opacity-50 [&>[data-slot=icon]]:-mx-0.5 [&>[data-slot=icon]]:my-0.5 [&>[data-slot=icon]]:size-5 [&>[data-slot=icon]]:shrink-0 [&>[data-slot=icon]]:text-[--btn-icon] [&>[data-slot=icon]]:sm:my-1 [&>[data-slot=icon]]:sm:size-4 forced-colors:[--btn-icon:ButtonText] forced-colors:data-[hover]:[--btn-icon:ButtonText] border-transparent bg-[--btn-border] dark:bg-[--btn-bg] before:absolute before:inset-0 before:-z-10 before:rounded-[calc(theme(borderRadius.lg)-1px)] before:bg-[--btn-bg] before:shadow dark:before:hidden dark:border-white/5 after:absolute after:inset-0 after:-z-10 after:rounded-[calc(theme(borderRadius.lg)-1px)] after:shadow-[shadow:inset_0_1px_theme(colors.white/15%)] after:data-[active]:bg-[--btn-hover-overlay] after:data-[hover]:bg-[--btn-hover-overlay] dark:after:-inset-px dark:after:rounded-lg before:data-[disabled]:shadow-none after:data-[disabled]:shadow-none text-white [--btn-bg:theme(colors.zinc.900)] [--btn-border:theme(colors.zinc.950/90%)] [--btn-hover-overlay:theme(colors.white/10%)] dark:text-white dark:[--btn-bg:theme(colors.zinc.600)] dark:[--btn-hover-overlay:theme(colors.white/5%)] [--btn-icon:theme(colors.zinc.400)] data-[active]:[--btn-icon:theme(colors.zinc.300)] data-[hover]:[--btn-icon:theme(colors.zinc.300)] cursor-default".

Even though you have neatly packed it into a component, the HTML rendered will have this very thing 500x repeated.

If I have on the other hand: className="main-button" or (horribile dictu) something like this in a larger codebase className="blog-post--list-item--button action-button" + a neat CSS class my bundle size will be much less.

Collapse
 
triloworld profile image
Patryk Padus • Edited

Maintaining is way better when obsolete code don't exist at all

CSS by invention is the inline approach best. That's why it is appealing. I tried all methods and before Tailwind was created I found that "utility first" way too powerful. Have idea to create a similar solution but Tailwind just works™ . Naming is one problem - there are a great number of solutions for structure. One way is SFC in Vue that helps organize all in native manner with helpers created from JS and for the web. We are deeply rooted in solutions that bring the most clean approach.

Utility first use naming as web native convention and add responsive option as option that isn't needed. Adding code in the same place where it belongs and focusing on more problematic issues than naming convention.

This solution 100% fixes naming, maintenance and even when there is too much class it shows where the issue is. We even think about the number of css classes to count or add ratio for issue when 20-50 classes are added in one node. But that's optimization and not something to bother. Vue should now have some automation going on for parts and in the future to rename into short hashes.