← All writing
Craft · · 7 min

Utility classes, reluctantly

On the position I held loudly, the argument for utilities that didn't move me, the one that did, and the compromise I can defend to a person who has to edit this in a CMS.

CSS

Two years ago I wrote a fairly pleased-with-itself post about BEM, in which the argument was that a class name should say what a thing is. .card__title, not .big-bold. The whole point of a semantic class is that when the design changes, the name still describes the thing, and you change one rule in one file.

I still believe most of that. And I have spent the last month on a project using a utility-first stylesheet, writing markup with pa3 mb4 f5 lh-copy on it, and somewhere around week two I stopped flinching.

So this is me working out which bits of my position were most important and which were taste I’d mistaken for principle.

The pitch I don’t buy

The standard case for utilities leads with separation of concerns, and it goes: the idea that HTML is content and CSS is presentation and they’re independent is a fiction, because in practice you can’t restyle anything meaningfully without also changing the markup, so you may as well stop pretending and put the styling where you can see it.

I’ve heard this a lot and I don’t find it convincing on its own terms. It’s true that the separation leaks. It leaks in every direction, and any honest developer has bent a document structure to make a layout work. But “this abstraction is imperfect” is not the same as “this abstraction is worthless,” and I’ve read the version of that argument that concludes with a <div> carrying 19 classes and I don’t think the person writing it has proved what they think they’ve proved.

The other thing people lead with is that you stop having to invent names, and naming is hard, and this is true and I find it mildly insulting as an argument. Naming is hard because the naming is where you work out what the thing is. Skipping it isn’t free.

The one that got me

Here’s what actually got me, and it isn’t an argument about aesthetics at all.

Every semantic stylesheet I have ever worked on gets bigger. Every single one, monotonically, for the whole life of the project. Nobody deletes CSS, because you cannot prove that a rule is unused, because the markup is generated by a template that’s generated by a CMS and the class might be in a content field somebody typed in 2015. So the file only grows, and the growth rate is roughly proportional to how many people have touched it.

And the growth isn’t new components. It’s variants. .card--compact, .card--compact-with-no-image, .card--sidebar, and then .promo which is a card with different padding and was copied from .card in a hurry. I have a stylesheet in front of me from a 2015 project with 400 lines of rules whose entire job is to adjust the margins of an existing component in one specific context.

A utility stylesheet doesn’t do that. It’s a fixed set of classes, generated from a scale, and it stops growing once you’ve got the scale. 200 sites could use the same file. The variation moves into the markup, and markup is the part of the system that’s already per-page, already deletable, already obviously coupled to the thing it’s next to.

Every semantic stylesheet I have ever worked on gets bigger, monotonically, for the whole life of the project. That's the argument, and it isn't about naming.

That’s a real property and I don’t have a good answer to it. The best I could manage was “well, be more disciplined,” which is what I’ve always said and has never once worked, including when the person being undisciplined was me at 5:30 on a Friday.

A compromise I can defend

Not all the way across. Roughly here:

Components stay components. A card is a card, it has .card and .card__title, and its internal look lives in one place with a name. That’s still the right shape for a thing that appears on 40 pages and needs to change once.

Utilities do the adjusting. All those --compact variants were never really about the component, they were about the space around it in a particular context, and that’s exactly what a spacing utility is for. So the card keeps its own styles and the context says mb-4 and I’ve deleted a modifier.

before / after
<!-- was: a modifier that exists to remove a margin -->
<article class="card card--flush">

<!-- now: the component, plus the spacing this context wants -->
<article class="card mb-0">
HTML

That split has held up for a month on real work, which is not long, and I’ll revisit it. The test I’m using is whether the class is describing the thing or describing its relationship to the page. The first is a component, the second is a utility, and the reason --compact variants breed is that they’re the second wearing the first’s clothes.

Three costs

Three things, and the third is the one that decides it on client projects.

The markup is genuinely harder to read at first. Not forever, and I got over it faster than I expected, but anyone new to the project hits a wall of abbreviations and the wall is real. Don’t let anyone tell you it isn’t there because they’ve stopped seeing it.

You need a templating layer or you’ll repeat yourself horribly. If a card’s 14 utility classes are typed out on every one of 40 pages, changing the card means 40 edits, and you have taken the single best property of CSS and thrown it away. In a Jekyll include or a WordPress partial it’s fine, because the repetition is in one file. Without one it’s untenable and I’d not attempt it.

Defending an artifact

The version of this I’d have written in 2015 was that utility classes are inline styles with extra steps, which is a good line and is basically wrong, because inline styles have no scale, no constraint, no media queries and no reusability, and a utility system is nothing but scale and constraint.

What I’d actually got attached to, I think, is the idea that a stylesheet is a model of the design and reading it should teach you the design. That was always half true at best. On most projects the stylesheet is a model of every decision anyone made in a hurry over four years, and I was defending an artifact I’d described rather than one I’d seen.

I’m still going to name my components. I’m just going to stop naming the gap between them.

Read similar posts
6 min

Style queries

Five surface variants, four child elements, and 20 selectors that existed only to tell a child which parent it was in. Style queries delete the whole grid of them. That isn't quite the same as saying you needed them.

6 min

currentColor

I opened a button component with 11 color declarations in it, across four variants, and the correct number was one per variant, because six of those properties already default to the thing they were being set to.