By Harry Roberts
Harry Roberts is an independent consultant web performance engineer. He helps companies of all shapes and sizes find and fix site speed issues.
Written by Harry Roberts on CSS Wizardry.
For the longest time, we’ve been saying that CSS doesn’t have logic. By that, we meant that there was no control flow or way of programmatically manipulating it. This inherent lack of logic has been used as an argument in favour of using preprocessors (to provide that missing feature), and as an argument against using preprocessors (CSS was never meant to have logic, so we shouldn’t go introducing it).
However, I recently hit upon a way of thinking that made me realise that CSS does include logic, and the fact that it’s rarely viewed as such is probably also why we end up with such poor CSS at all.
I found myself explaining compound selectors to a client as being made up of the subject—the thing we’re actually interested in—and its conditions. For example:
div.sidebar .login-box a.btn span {
}
In this compound selector, the subject is span
, and the conditions are IF
(inside .btn) AND IF (on a) AND IF (inside .login-box) AND IF (inside .sidebar)
AND IF (on div)
.
That is to say, every component part of a selector is an if
statement—something that needs to be satisfied (or not) before the selector will
match.
This subtle shift in the way we look at how we write our selectors can have a huge impact on their quality. Would we really ever write (pseudo code):
@if exists(span) {
@if is-inside(.btn) {
@if is-on(a) {
@if is-inside(.login-box) {
@if is-inside(.sidebar) {
@if is-on(div) {
# Do this.
}
}
}
}
}
}
Probably not. That seems so indirect and convoluted. We’d probably just do this:
@if exists(.btn-text) {
# Do this.
}
Every time we nest or qualify a selector, we are adding another if
statement
to it. This in turn increases what is known as its Cyclomatic Complexity.
In software engineering, Cyclomatic
Complexity is a metric
which concerns itself with the number of ‘moving parts’ in a piece of code.
These moving parts are usually points within some control flow (if
, else
,
while
, etc.), and the more of them we find, the greater our Cyclomatic
Complexity. Naturally, we want to keep our Cyclomatic Complexity as low as
possible, as greater complexity means that, among other things,
Applied to CSS, we’re basically looking at the number of decisions a browser has
to make before it can or cannot style something. The more if
statements in
our selectors, the greater the Cyclomatic Complexity that selector has. This
means our selectors are more fragile, because they have a greater number of
conditions that must be satisfied in order for them to work at all. It means
our selectors are less explicit, because introducing if
statements
unnecessarily can lead to false positive matches. It makes our selectors far
less reusable, because we’re making them jump through way, way more hoops than
they need to.
So Instead of binding onto a span
inside of a .btn
(and so on) we would be
far better off creating a new class of .btn-text
to bind onto. This is far
more direct, as well as being more terse and more robust (more @if
s lead to
more brittle selectors that have a greater chance of breaking).
The trick is to write your selectors how the browser parses them: right to left.
If your first question is is there a
then your net is being cast
far too wide. This is why you then have to qualify the span
?span
with so many
conditionals: to narrow the selector’s reach. You need to start at the other
end; write something unambiguous and explicit and forgo the conditions entirely.
Instead of your selectors casting a really wide net that catch way too much of the DOM—and then having to trim that catch down via conditions—it is far more succinct and robust to just catch much less of the DOM in the first place.
Cyclomatic Complexity is quite an advanced principle to try and apply to CSS, but if we look at it as just that—a principle—we can start to visualise and even measure the complexity in the ‘logic’ powering our selectors, and we can then begin making much better decisions based on it.
Some good rules of thumb:
if
statement; read these if
s out loud to yourself to try
and keep your selectors sane.Your selectors are the most fundamental aspect of your CSS architecture; make sure you’re writing sane and simple ones.
Harry Roberts is an independent consultant web performance engineer. He helps companies of all shapes and sizes find and fix site speed issues.
Hi there, I’m Harry Roberts. I am an award-winning Consultant Web Performance Engineer, designer, developer, writer, and speaker from the UK. I write, Tweet, speak, and share code about measuring and improving site-speed. You should hire me.
You can now find me on Mastodon.
I am available for hire to consult, advise, and develop with passionate product teams across the globe.
I specialise in large, product-based projects where performance, scalability, and maintainability are paramount.