I named it after the page
Someone on the account team asked whether the promo box on a client’s landing page could sit a little closer to the heading above it.
That’s a reasonable request and about 15 minutes of work, most of which is finding the file.
So I went to add .promo--tight, and .promo--tight was already there. It takes the bottom margin off, on the category template, and somebody wrote it a year ago for a reason nobody wrote down anywhere. I needed the top margin, on one page, and only on that page.
I wrote .promo--tight-landing, saved it, and then sat there looking at it for a while.
A class name with a page name inside it isn’t describing a thing. It’s a coordinate.
Tight was taken
I went looking for the rest of the family, which took no time at all, because they were all sitting together at the bottom of the file where that sort of thing goes.
.promo--tight { margin-bottom: 0; }
.promo--flush { margin-top: 0; }
.promo--compact { margin-top: 1.5rem; }
.promo--sidebar { margin-bottom: 1rem; }
.promo--tight-landing { margin-top: 0.5rem; }
Then I got curious about the rest of the stylesheet, counted every rule whose entire body was a margin declaration, and gave up somewhere past 400 lines. Buncha margins.
Every one of those was written by a competent person answering a real request. That’s the part I couldn’t get around. Not one of them is about the component it’s attached to. They’re about where the component happened to be standing at the time, and the component has no way of knowing that, so the knowledge got encoded in a name instead.
It landed, I think, because I’d spent most of the month before on a different client’s build using Tachyons, writing markup with pa3 mb4 f5 lh-copy on it and quietly hating every minute of the first week. Somewhere in week two I stopped flinching. So the argument arrived while I was already in the middle of the counterexample, which is not how I’d have preferred to be convinced of anything.
The pitch I don’t buy
The standard case for utilities leads with separation of concerns, and the strongest version of it is that the separation was never real. HTML is content, CSS is presentation, the two are independent, except that you cannot restyle anything meaningfully without also touching the markup. So we may as well stop pretending and put the styling where we can see it.
I’ve heard that a lot and I don’t find it convincing on its own terms. The separation does leak, 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 claim as “this abstraction is worthless,” and every version of that argument I’ve read ends up at a <div> carrying 19 classes, which I don’t think proves what the person writing it thinks it proves.
The other thing people lead with is that you stop having to invent names, and naming is hard. I’ve always found that mildly insulting as an argument. Naming is hard because the naming is where you figure out what the thing is. Skipping it isn’t free.
I’d like to keep saying that. I can’t quite, because I’d just spent an afternoon on a request with nothing in it to name. Nobody was ever going to figure out what .promo--tight-landing is by thinking harder about it. It isn’t anything. So maybe the argument isn’t insulting so much as narrower than the people making it tend to say, and the narrow version is the one I have to answer.
Nobody deletes CSS
What actually got me isn’t an argument about aesthetics at all.
Every semantic stylesheet I have ever worked on gets bigger. All of them, monotonically, for the whole life of the project. Nobody deletes CSS, because you can’t prove a rule is unused, because the markup comes out of a template that comes out of a CMS and the class might be sitting in a content field somebody typed in 2015. So the file only ever grows, and my sense is that the growth rate is roughly proportional to how many people have touched it.
The growth isn’t new components either. It’s variants. I built a style guide nobody opened again last year and then found three button variants in production that weren’t in it, which is the same thing happening to a component I had already written down. Nobody sat down to design a fourth button. Somebody needed the existing one to be different in one place and had nowhere to put that except a new name.
I thought BEM had solved the deletion problem, and it solved half of it. I can search a block name and remove a whole component with some confidence, which is genuinely the thing I was most pleased about at the time. It does nothing for modifiers, because a modifier is used on one page, and finding which page is the entire difficulty.
A utility stylesheet doesn’t have this problem, and the reason is mundane. It’s a fixed set of classes generated from a scale, so it stops growing the moment the scale is settled.
$space: (0: 0, 1: 0.25rem, 2: 0.5rem, 3: 1rem, 4: 2rem);
@each $key, $value in $space {
.u-mt-#{$key} { margin-top: $value; }
.u-mb-#{$key} { margin-bottom: $value; }
}
10 rules, and the file is finished. It will be the same length in two years, on this project and on the next few, and the variation it used to absorb moves into the markup, which is already per-page, already deletable, and already coupled to the thing sitting next to it.
That’s a real property and I don’t have a good answer to it. The best I could manage was “be more disciplined,” which is what I’ve always said and has never once worked, including the many times the person being undisciplined was me at 5:30 with the week nearly over.
The space belongs to the page
I haven’t gone all the way across. Roughly here:
Components stay components. A card is a card, it has .card and .card__title, and its insides live in one place under a name that says what they are. That’s still the right shape for something that appears on 40 pages and has to change once.
Utilities do the adjusting. Every --tight and --flush I have ever written was about the space around a component instead of the component, and the space around a thing belongs to whatever put the thing there.
<!-- was: a modifier holding the name of one page -->
<aside class="promo promo--tight-landing">
<!-- now: the component, plus what this context wants -->
<aside class="promo u-mt-2">
The test I’m using is whether the class describes the thing or describes where the thing is standing. The first is a component and the second is a utility, and the reason the --tight family breeds is that it’s the second wearing the first’s clothes. That has held up for a month of real work, which is not long enough to mean much. I’ll revisit it.
Three costs
The markup is harder to read at first. Not forever, and I got over it faster than I expected, but anyone new to the project walks into a wall of abbreviations and the wall is real. pa3 mb4 f5 lh-copy means something to me now and meant nothing to me in May. Don’t let anyone tell you it isn’t there just 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, then changing the card is 40 edits, and you’ve taken the single best property CSS has and thrown it in the trash. In a WordPress partial it’s fine, because the repetition lives in one file and gets edited once. Without one I wouldn’t attempt it.
And specificity, which is the one I have no answer for. The whole job of a utility is to overrule whatever the component already said, and .u-mb-0 is a single class, so it only overrules .card__footer by sitting further down the file, and it loses outright to anything anybody ever wrote as .sidebar .card__footer. Which is why every utility library I’ve looked at puts !important on all of them, and that does work, and it also means the utility can’t be overridden by anything else either. You’ve traded a cascade you could reason about for a hammer.
An artifact I’d described
The version of this I’d have written two years ago is that utility classes are inline styles with extra steps, which is a good line and is basically wrong. Inline styles have no scale, no constraint, no media queries and no reuse. A utility system is nothing but scale and constraint.
I’d actually gotten attached, I suspect, to the idea that a stylesheet is a model of the design, and that reading one should teach you the design. That was half true at best. On most projects the stylesheet is a model of every decision anybody made in a hurry over the years. I was defending an artifact I’d described instead of one I’d gone and looked at.
.promo--tight-landing is still in that file. I know exactly what it does and exactly which page it’s on, which makes it the single most deletable rule in the whole stylesheet. I haven’t deleted it, because the thing it’s doing is still wanted. That’s what the position never had room for. Two years of arguing that a name should describe what a thing is, and the request actually in front of me was a person wanting one block on one page to sit a little closer to the heading above it, forever, which is real and reasonable and has no honest name. I could keep the principle or I could answer the request. I don’t think I noticed I was choosing.