Don’t make the stylesheet grep for you
Published 2010-03-12
For the last decade or so, every time I wanted to design something like a “subheader,” I’d code it like this:
<h3 class="subheader">This is a subheader</h3>
And then I’d define a style in the main stylesheet like this:
.subheader {
color: #f00;
font-size: 18px;
}
Which would, of course, overload any styles I’d already defined for <h3>
:
h3 {
color: inherit;
font-size: 20px;
}
Sooner or later, we’d realize that we wanted the subheaders in another part of the site (like on the “siderail”) to look a little different. So the HTML didn’t change, really:
<div id="siderail">
<h3 class="subheader">This is a subheader</h3>
</div>
So then we’d overload the .subheader
selector:
#siderail .subheader {
color: #900;
font-size: 14px;
}
So far, this seems pretty simple, right? h3
is defined with a certain bunch of characteristics, unless it’s a subheader, in which case some of those characteristics get overloaded. Or if the subheader is in the siderail, in which case some of those characteristics get overloaded. This is what the “cascading” in “Cascading Style Sheets” means, right?
Until, of course, one of the developers forgets to add the subheader class to an h3
in the siderail. Or creates a new page region in the siderail where we want to apply a new style — forgetting that #siderail .subheader
already overloads h3.subheader
. And rather than diagnose where in the cascade the design is breaking, we just add a new selector, maybe a new class like .specialsubheader
, that does what we want. And maybe can’t quite remember which shade of dark red (#900
) wanted for subheaders in the sidebar, so we make a best-guess (#a00
) ... which lets the look and feel drift a little bit.
(There’s also an orthogonality problem with this, which had never occurred to me before. I was mixing in semantic units [“subheader”] where I should have been sticking to cosmetic units [“darkred”]. An h3
should always be a “subheader,” whether it’s in the siderail or not). I noticed this most painfully in places where I used junk like <div class="subheader">
. Why do I need “subheader” to tell me what that div
should be doing? div
s and span
s are just empty containers — if you strip their styles away, the content should still cohere semantically).
Well, after only a year of this, we’d reached a point where our main CSS file was 2000 lines long. Most pages loaded 500 to 1000 CSS selectors. When I autopsied the stylesheets I found about 20 different shades of “brown.” That’s just nuts.
So, this time out the gate, I defined a bunch of selectors — mostly classes — with really generic behaviors. Font sizes, padding, foreground colors, background colors, that kind of thing. I also defined some “default” behaviors for semantic units (usually vanilla HTML tags like h3
).
So, to extend our example above, the stylesheet might have some selectors like:
h3 {
color: inherit;
font-size: 20px;
}
.fgred { color: #f00; }
.fgdarkred { color: #900; }
.smallfont { font-size: 14px; }
.mediumfont { font-size: 18px; }
This treats the cosmetic styles kind of like Legos. Where I need a certain effect, I can mix and match styles to achieve it:
<div id="siderail">
<h3 class="fgdarkred smallfont">This is a subheader</h3>
</div>
As a coder, this verbosity kinds of offends me. But in addition to saving a lot of mad selector proliferation — itself a form of (harmful!) verbosity, this verbosity serves four useful purposes:
- It separates cosmetic and layout styles (“darkred”) from semantic selectors (“subhead”). In fact, it generally lets vanilla HTML (“h3”) do the semantics.
- It expresses cosmetic and layout styles where they are most valuable: in the template.
- It allows me to easily experiment with styles in the template.
- It creates a single, predictable namespace for future selectors.
I just finished refactoring MercyCorps.org according to these principles. I was surprised at how the design disciplined itself. I took a few shortcuts — mostly to work around core Drupal or contributed modules I couldn’t or didn’t care to rewrite — but this self-discipline was just amazing. Instead of building every new template from the ground up, I could go to my big box full of style Legos and put together an effect really rapidly. If I lacked a particular Lego, and couldn’t get a good combination together to replicate it, I’d add it to the Lego box, but as a generic (not nested) selector. That way I’ll have it around to use later.
When I first stumbled across Nicole Sullivan’s “Object-Oriented CSS”, I was dubious about the philosophy. (I still hate the term “Object-Oriented CSS” — CSS is promiscuous, global, and amorphous by design — you just can’t treat selectors like objects, period.) But as I started building my box of Legos I came to think she doesn’t take it nearly far enough. I ultimately split my style “families” into color, text treatments, semantic selectors, layout selectors, and custom selectors. Most of my selectors are generic classes and have one, or at most two, rules. I refactored the entire Mercy Corps site — some 300 templates, I reckon — solo, in under a month, while dealing with our response to the Haiti Earthquake. It required minimal changes to the CMS (mostly redistributing blocks and redefining a few imagecache presets — and these changes were prompted more by our transition to the 960 grid than because of the CSS.) Because the new selectors are in a single namespace, if I need to undo them in multiple templates (for example: change all fgdarkred
to fgdarkgreen
, I can do this easily with grep.
Speaking of grep, the biggest shift in this exercise has been the way I’ve reimagined my workflow. For much of my career I’ve spent most of my time looking at CSS code. If I needed to change a style, I’d have to figure out a way to do it there, and construct careful namespaces to avoid clobbering selectors elsewhere. Now I don’t need to look at the template (to find the selector) and the CSS — I just edit the template. I realized that for the last decade or so I’ve been using CSS as grep. “Find all the places where subheader
is inside siderail
and make it darkred
” That’s so backwards. I said this elsewhere:
Don’t make the stylesheet grep for you.