I’m not here to raise a shield protecting CSS utility frameworks. I don’t even particularly like the approach, myself, and nothing is above fair criticism. But fair is a key word there. I can’t tell you how many times I’ve seen utility styles compared to inline styles. Sarah Dayan is weary of it:
[…] despite numerous attempts at debunking common fallacies, utility-first enthusiasts keep on having to reply to a staggering amount of misconceptions. And by far, the most tired, overused cliché is that utility classes are just inline styles.
I think this comparison will make it clear:
<div style="color: #3ea8ca;"></div>
<div class="color-blue"></div>
The first div has a color
set directly in the HTML that is an extremely specific blue color value. The second has a color
that is set outside of the HTML, using class name you can use to configure the shade of blue in CSS. Sure, that second one is a fairly limited class name in that, as the name suggests, does one job, but it still offers some abstraction in that the blue color can be changed without changing the markup. It’s the same story with a sizing utility class, say size-xl
. That’s also an abstraction we could use to define the padding of an element in CSS using that class name as a selector. But if we were to use style="padding: 10px;"
directly on the element in the HTML, that is an absolute that requires changing the value in the markup.
To be fair though (which is what we’re after), there are quite a few classes in utility frameworks that are named in such a way that they are extremely close acting like inline styles. For example, top-0
in Tailwind means top: 0
and there is no configuration or abstraction about it. It’s not like that class will be updated in the CSS with any value other than zero because it’s in the name. “Utility” is a good way to describe that. It is very much like an inline style.
All that configurable-with-smart-defaults stuff puts utility-based frameworks in a different category. Inline styles offer no constraints on how you style things (other than hard limitations, like no pseudo selectors or media queries), while a limited set of utility classes offer quite a lot of styling constraints. Those constraints are often desirable in that they lead to a design that looks consistent and nice instead of inconsistent and sloppy.
To borrow a metaphor I heard in a slightly different context one time: Utility-class frameworks are like bumper bowling for styling. Use the classes and it’ll work out fine. You might not get a strike, but you won’t get a gutter ball either.
Another unfair criticism I hear in conversation about utility frameworks is that you ship way more CSS with them. If you are, then you’re definitely screwing up. In my mind, the main point of this approach is shipping less CSS (only the classes you use). I’m the first to tell you that a build process that accurately and perfectly does this is tricky and could lead to an unhealthy amount of technical debt, but I’ll cede that if you do it right, shipping less CSS is good for performance. Tailwind in particular highly encourages and helps you do this.
So all that said, I think there is all sorts of stuff to criticize about the approach. For example, I personally don’t like looking at all those classes. I just don’t. I’m not an absolutist about perfectly abstract classes, but seeing 10-20 classes on div after div gets in the way of what I’m trying to do when I’m templating HTML. It feels harder to refactor. It feels harder to see what’s going on semantically. It’s harder to parse that list for other classes that I need to do non-styling things. Some of the advantages that I would get from utilities, like scoping styles to exactly where I need them, I often get through other tooling.
I also think utility-frameworks work best in JavaScript component setups where you have Hot Module Reloading. Otherwise, HTML changes tend to trigger entire page refreshes. For example, a tool like Browsersync is pretty darn nice. It does CSS injection when your CSS changes. But it can’t do new-HTML injection; it just refreshes the page. So without Hot Module Reloading, which generally ain’t for your generic HTML site or Static Site Generator, you get worse DX while authoring.
I think the point in comparison is that the problems with inline styles are the same as utility classes. Obviously syntactically they’re different, but the fact that you can abstract what “blue” means doesn’t really solve (at least what I see as) the main problems with inline styles (which is the violation of separation of concerns and the DRY principle). I mean, if abstraction really was the only problem, you could always do
Which imo isn’t much better, and isn’t any different from
It’s indeed very similar, but there’s a huge difference between the two.
It’s called the Content Security Policy, CSP for short.
Using
style
is an instant failure at security while using a class is 100% fine.For the record, I don’t like either method as I feel it creates a hard link between the styles and the content, which to me is bad for separation of concerns. I just wanted to point out that using
style
has greater downsides than using an utility classUtility CSS framework IMO work best with Component based frameworks. I think this is how you stay DRY.
When using things like React I always do one to one mapping between my component and its style, and never use CSS across multiple component.
If I have a UI pattern that repeats and needs to look the same (icon + text, button etc…) then I’d simply extract it as a reusable component with its own style.
I think in those case it doesn’t really change much whether you declare your CSS in a separate file, use Styled Components or use Utility CSS frameworks, as your CSS is tied to one single piece of HTML template anyway
I don’t see this being feasible at scale. Do you really have to create a whole new component for every HTML primitive that needs to have a certain style? Seems like controlling the styles for most everything except layout templates and components would be far easier in a stylesheet. I just don’t see how this
is any better than just
Now, if we’re talking true abstractions of unique, reusable patterns, that’s one thing. I’d still favor semantic classes, separation of concerns, stylesheets-as-SSoT, etc., but IMHO it’d at least be better than having to create new components for everything HTML and CSS gives you for free.
Agree with Jace Cotton.
Plus: so called utility putting more headache on developers, you need to learn them but in reality css knowledge should be sufficient as utility = inline.
Therefore what’s the point. Learn how to use clases first before writing them. You have header on all pages, css is needed on all pages =>use external. You need to have specific css on one page use additional style sheet inline or external. Why you need to complicate stuff? You planning to add 10 modules, means you need to refactor your approche and serve 1 module with 10 modules plus 1 inline or external css. You want to do something dynamic = chaos as you never should give customer to decide how your website looks like => they are not designers, they mostly don’t know what they want et cetera.
I think Tailwind really nailed the utility approach pretty nicely.
The two biggest tradeoffs for utility frameworks, in my opinion, are that they make responding to media queries cumbersome and that each utility REALLY needs to do only one thing.
I don’t love the approach either, but again, an automated solution like Tailwind solves the problems it wants to solve pretty well.
Using Tailwind in HTML is great for prototyping or small variations of predefined classes, but not great for just leaving in production, I suggest using
@apply
in you css, so, you can clean up and merge common styles, and also, so you can use tools like Browsersync.This is what we do. Prototype rapidly with in-line utilities from tailwind, then use —-purge to keep only what we need, and by the time production rolled around will have much of the classes added with @ apply. Keeps things relatively clean and if I need to make random one off changes, I’m ok leaving a few basic utils peppered about
One issue some may thing is that, even if you remove unused CSS, you may need to ship more HTML. As an example, a small project of mine with uses Tailwind have a component that uses a 77 character class and this component is repeated 24 times in a page, so it adds up in the generated HTML. Larger projects may have components that repeat a lot more than this one.
If you’re using client-side rendering it’s not a issue as those classes are defined once in the components. In other cases using Tailwind’s
@apply
to reduce multiple used classes into a single one may help.But I don’t think that’s a big issue: HTML is compressed anyway. Gzip is great, Brotli does wonders. If you aren’t using any of those you’re also screwing up.
I think is very good but as the author said, only for environments with HMR, and environments that are JS-centric, because when using this approach, the only way to abstract a set of styles (aka utility classes) is to create a js component. No multiple file extensions. No intermediate libraries. You want an abstraction? You create a js file for that. And I think thats a pretty neat “joining of concerns”
Thank you for addressing this. While for basic examples there are few abstractions, you use more when you use things like media queries. For example, to make a grid have five columns when the screen size is above the specified large breakpoint, in Tailwind it is just
That includes the media query and everything. In comparison, the CSS for that is
The CSS is a lot longer, and you can’t even do media queries with inline styles. However, I respect separating concerns, although, with Tailwind’s @apply, it is still possible to take advantage of the abstractions that make responsive design easy.
The vanilla CSS approach could definitely be condensed. First, the
minmax()
function isn’t needed there. Secondly, you can abstract therepeat
number as a variable, so setting up other column layouts would be extremely concise after the initial setup.I think a more elucidative comparison/example would be something like
versus
(Assuming Sass of course, but it could be done without.)
While the vanilla approach is obviously less concise, it also gives you far more flexibility (say if you didn’t want all the same-sized columns for example), and the more “exactness” required to set it up helps me reason about it better and just feels a lot more freeing.
I totally agree with your assessment that “Utility-class frameworks are like bumper bowling for styling.” In fact, I’d actually prefer that.
It’s about using consistent patterns and building blocks for design.
One argument is about being DRY. You can totally use @import statements in your css classes for that reason.
Utility classes are great for utility styles. They’re less great for other styles. All of the things here are true and fair. But it’s also the composition layering issue. If you have to recompose something repetitive using the same 15 low level utility classes, things become less manageable.
The two main issues I have with “utility classes” (I’ll be honest: Tailwind’s the only one I’ve tried) both have to do with performance.
First off, sure, the CSS is smaller—often vastly smaller—but that comes with a price of inflated document size (all those class names do add up). Sure, we’re probably talking about a (properly trimmed) CSS file that’s hundreds of kB smaller than a “normal” coding style like (A)BEM or SMACSS, vs HTML that’s “merely” few kB larger. However, which of those gets cached by the browser after the first visit to the site? That’s right: CSS.
The other issue I have is that, with the rise of HTTP/2, server-push, and modular components, having all-in-one CSS files is going the way of the dodo. Instead, the future of CSS is in multiple smaller CSS files, each specifically tuned & customized to style a single component of a site. Now, that approach doesn’t preclude still using Tailwind (et al), but it does make it counter-productive to do so: instead of one
.top-0 { top: 0; }
rule in an all-in-one CSS file, you’d likely have something like one in each individual CSS. Multiply that by the multitude of utility classes likely to be used in more than one component, and the end result is massive, needless bloat for no reason.I didn’t understand the need of css-in-js. And I stopped using Atomic CSS-html since 2016.
Separation of concerns
Ugly and unreadable Html
More difficulty to get responsiveness
CSS offers answers with modules and variables. So using Atomic or Tailwind is overengineering.
Stay KISS, with less libs, less frameworks. CSS ones wont help you much.
I kinda hyped Tailwind, but now after actually trying to use it on an actual production app, have to say I kinda regret it.
Very long class names, not to mention that it’s just sort of unknown how you should order the classes in a way that makes sense.
Things like style variants are actually a lot harder on Tailwind, you end up with these class join helpers, which is sorta bizarre to me.
I have this idea where you have a special CSs syntax for a group of atomic classes, which you can then apply to an element, sort of different from @apply where it extracts styling.
The benefit I see is that you can organize the individual classes easily, and it also lets you compose those groups.
It might also help in composing variants, but I’ll have to see it in practice.
To be fair it’s still a great tool, for prototyping that is. Tailwind Play is something I visit a lot for prototyping certain components and ideas.
Svelte is worth a look.
It takes styles from anything declared inside a < style > block of a component.
It’s then pre-rendered at build time then gets written into the < head > on any pages that use the component.
It’s a super dope framework, I’m using it on my next pet project.
https://svelte.dev
Because I agree with the claim that criticism of anything must stand on fair grounds, I must seize the opportunity here to apply this doctrine to Tailwind users’ criticizing of CSS.
Bad faith arguments, comparisons and strawmen are as frequent on their side of the debate.
Things like :
“System” Values reduce Magic Numbers” are not exclusively true for Tailwind. The same approach is entirely feasible with CSS, with minimal effort.
The same is true for “Responsive Design in the Browser”. Not a Tailwind advantage per se.
“Inlining Styles reduces Naming” isn’t, again, a proper Tailwind advantage. It only is when compared with dogmatic methodologies like BEM or OOCSS. Armed with an understanding of how specifity and the cascading nature works, one can absolutely leverage these features to minimize the need for naming. It only is truer with the advent of
:where()
. The only really needed name to come up with in pure CSS is your module’s. As it is with Tailwind.The CSS examples given by Tailwind proponents to justify its usage are generally badly constructed, poorly selected or wrongly intended rulesets.
Selectors such as these are common as examples of CSS’ issues :
The problem here is that it is, in fact, a badly, really badly formulated piece of CSS selection. But you shouldn’t be creating these bad selectors to begin with! And the good news is you don’t have to. Way better, clearer and intelligent CSS can and should be developed. This is not a problem with CSS, it’s a problem with how you (and probably your colleagues) write CSS.
I can’t shake the feeling that Tailwind and similar are used, in many ways, to answer problems that the Tailwind user has himself created. By correctly wielding CSS’ powerful features many an argument in favor of Tailwind collapses. Combined with the true problems of the atomic CSS paradigm (not the unfair ones mentioned in this article), the native and natural way of writing intelligent, purposeful and explicit CSS can and should be considered a lighter and simpler alternative to atomic frameworks and dogmatic methodologies.
Or we, as a community, can continue solving problems we ourselves create by adding complexity, dependencies and bloat to reimplement native features of a misunderstood design language. I think we should try to use the design language we are given by the Web platform intelligently before trying to avoid it.
It’s not clear why you believe that CSS selector example is badly formulated.
It’s badly formulated because it selects the
div
and thea
as unnecessary intermediate tags. If you want to style theh2
and theimg
tags, select them directly, without intermediates. These intermediate tags should only be part of the selector if their presence changes styling needs.My bet is that their presence does not change anything and they should then be left out.
Amen.
I don’t know or care how many devs agree with this:Writing strong, thought-out CSS code is an art form that takes years to truly master.
Hand coding a style guide from scratch gives you the knowledge and power to confidently import, bundle, concat, and inject your styles in a logical way.
From then on you can compile all layout, helpers, utilities, etc into a single uglyfied, compressed and cached main CSS file.
Load it once and it handles everything except unique styles pertaining to 1-off
components.
Loading a single file might sound daunting and at antiquated but hand-writing great CSS is inherently lightweight.
Bring back Static sites!
Food for thought
Well interesting to see. :3
I actually use Tailwind, and rarely really look back. If you are accustomed to the class names and the system, it works pretty well.
Don’t get me wrong, I enjoy writing vanilla CSS, did SCSS and worked with BEM as a concept for some years. But Tailwind sticks. It does work much better in a component based system. It takes out the pain points I experienced with BEM. Naming things, often jumping between HTML/PHP and CSS/SCSS.
The question we should ask ourselves: Do our styles have to be so tightly coupled to the HTML? HTML is very semantic, but does CSS have to copy just that? I can’t tell for sure, it’s just some things I think about.
One pain point on Tailwind currently for me is it’s bloat. It does look very big for new users, and people, who don’t know how to control the output size or purge unused styles. And a fallback option for old browsers like IE11 would be great. Just sayin’. :)
I like this example. I didn’t like seeing this in 2010 – and so, I’m not sure why we’re rushing to double down in 2021.
Now, “pull left” isn’t true and it’s also really unhelpful for the Jr. Dev who I’m trying to get to do all of my work for me.
I think that everyone means well. I’ve met people at meetups who were highly productive on the front-end with very limited knowledge of CSS and so, there’s a reason these things are well liked.
I talked a little bit about how utility classes are similar to how you might use mixins (but also not) in my CSS-tricks article: “On Type Patterns and Style Guides”. Any thoughts on that?
One thing that inline styles cannot do is responsiveness, tailwind has the responsive classes you can put before your styling classes to change how your element looks based on size, e.g.
text-lg md:text-xl
. Same with hovers, I’m pretty sure you can’t do that with inline styles.After doing BEM, SMACSS, semantic class names, etc, for a decade, I tried Tailwind and I never want to work with anything else. So much easier to avoid unintended consequences in large code bases.
While this is true for 0, I don’t know that I look at 0 in the same way. The
-0
classes read to me more like “remove” and 0 happens to be the baseline for removing the distance.None of the other numbers align strictly with themselves, but rather with a scale of rem units. For example with padding classes p-2 doesn’t correspond to 2rem, but 0.5rem. The numbers are an abstraction from needing to come up with words for each sizing when the sizes are more complex than small, medium, large and
-0
just happens to correspond with 0rem.