Cascade, Specificity and a Single name space
or: How I learned to stop worrying and tolerate CSS
Cascade
The cascade, it's the 'C' in 'CSS' so it's pretty important yet it's often not very well understood. Essentially the cascade says that - all other things being equal - the last declaration wins.
What this means is that CSS is dependent on source order: the same rules declared in a different order can have different effects.
Rules Cascade
Lets have a look at an example of what that means in practice:
Given the following markup:
<div class="base alt">…</div>
This CSS:
.base {
color: red;
}
.alt {
color: green;
}
is not the same as:
.alt {
color: green;
}
.base {
color: red;
}
In the first case we have the base
class declared first as being red and then we have the alt
class as green. As both classes are applied to our element the second declaration takes precedence as it's later in the cascade - or source order
In the second case we have reversed these two rules in the source and the effect is reversed. The markup is the same, the rules are the same but the order has changed so the cascade is different and the effect is different.
Now, it's fairly easy to understand what's happening here but imagine these rules are hundreds of lines apart in your source code and being used in different places across the site. If you're not thinking about this and actively managing the cascade your code is going to be harder to maintain.
Declaration Cascade
The cascade doesn't just apply to rules, it applies to declarations within rules as well:
.class {
margin: 1em;
margin-top: 0em;
}
is not the same as:
.class {
margin-top: 0em;
margin: 1em;
}
We can see in these examples that in the second example the margin-top
property won't take effect as it's being overridden by the margin
property below it.
This is easier to manage than the rules cascade, especially if we're keeping the number of declarations in our rules to a minimum. However, we can help ourselves out by choosing a convention for ordering our rules. What we want to avoid is using an arbitrary sort order - such as alphabetical - or just stuffing declarations in at the end of the rule as we go alone.
Specificity
If you're thinking the cascade was bad then specificity can get you into even more trouble.
With the cascade the last rule in source order applies. With specificity the rule with the greatest specificity selector applies. Specificity is independent of source order but if you've got two rules with the same specificity then the cascade comes back into play and source order applies again.
Specificity calculation
Working out the specificity of a selector can get a bit confusing but is actually logical. The specificity of a selector is expressed in a four column matrix ( [W,X,Y,Z]
) and the highest specificity in the lowest index wins.
To build the matrix you break the selector down into its component parts and add them up. Looking at the table here we can see the specificity for each component.
Selector component | Specificity |
---|---|
* | [0,0,0,0] |
element | [0,0,0,1] |
class | [0,0,1,0] |
ID | [0,1,0,0] |
inline | [1,0,0,0] |
- The global selector (
*
) has no specificity - which makes sense, it's not specific: it applies to everything. - Elements are the least specific as they apply to all content of a certain type (list, heading, link), they are in the final column.
- Next up are classes which are more specific than elements, we have to use elements in HTML but we can choose to apply classes to them.
- Followed by IDs as they are very specific since there can be only one of any given ID on a page.
- And finally we have inline styles which you should try and avoid.
Here's an example of how to apply this:
.class #id => [0,1,1,0]
.class .class => [0,0,2,0]
In the top selector the single ID in the second column trumps the two classes in the third column in the bottom selector. Note that because this is a matrix, any number of classes will be still be trumped by a single ID.
Specificity and cascade
Specificity overrides the cascade in that if you have two rules with the same property declared then, regardless of the source order, the one with the higher specificity will take precedence.
.class {
color: red;
}
#id .class {
color: green;
}
is the same as:
#id .class {
color: green;
}
.class {
color: red;
}
In this example we can see that the additional specificity of the ID means that it is taking effect even when it is earlier in the cascade.
Single name space
CSS has a single name space which means that if we're not very careful and disciplined we'll inevitably end up with conflicts. This is pretty much how all CSS code bases of any size get out of control.
Let's have a look at an example of how this happens. Admittedly this is a bit of a contrived example but if you haven't encountered this type of situation then you haven't written much CSS.
Once upon a time…
To start with we have an error presentation that we're using across the site:
.error {
color: red;
}
Time passes…
Some time later we get a request to change the error presentation on the login page. We open up dev tools, we inspect the element and we can see it's got a class of .error
. We change the colour from red
to orange
and it all looks good.
.error {
color: orange;
}
However, because there was only one form on the login page what we didn't realise was that we've just changed all the errors across the site. Next thing we know there's a P1 "blocking" bug assigned to us saying that 'all the errors on the site are broken'. We reduce the severity of the bug to "cosmetic" and take a look at what's going on.
Cascade
What's happened is that our second, orange
, declaration is occurring after the first, red
, declaration so it's taking precedence in the cascade.
.error {
color: red;
}
.error {
color: orange;
}
So we open up dev tools again, have a look at the login page and see if there's something else we can work with.
Specificity
It turns out there's an ID of login-form
wrapping the fields so we can use that to target just the errors in that form:
.error {
color: red;
}
#login-form .error {
color: orange;
}
What's happening here is that the second declaration uses a more specific selector so any error contained within that form will be orange
whilst the rest will remain red
. Job done.
At this point it must be 5 o'clock somewhere in the world so we stick a fork in it and have a desk beer.
Single name space
We now get a request to change the colour of error messages for email fields to blue
.
Not a problem, there's already a class of email
on the fields so we can just add an additional class to the selector and we're good:
.error {
color: red;
}
.email .error {
color: blue;
}
#login-form .error {
color: orange;
}
But unfortunately this selector has lower specificity than the one used to 'fix' the login form errors. We can't up the specificity of the email field selector so we'll add an !important
annotation:
.email .error {
color: blue !important;
}
The way !important
works is by resetting the cascade. Effectively there's now a second cascade of declarations that have the !important
annotation so here the .email .error
colour will now trump the higher specificity selector for the login form because that doesn't have an !important
annotation.
Go through this process a few times on different parts of your code base and you will end up...
Preprocessors
I don't want to spend much time on preprocessors but we need to cover them.
They are an authoring tool and as such they make it easier to write CSS which is a double edged sword.
They make it easier to write good CSS but they also make it easier to write bad CSS.
This quote from Charles Babbage is one of my favourites and applies quite well here.
On two occasions I have been asked, 'Pray, Mr. Babbage, if you put into the machine wrong figures, will the right answers come out?' I am not able rightly to apprehend the kind of confusion of ideas that could provoke such a question.
The quality of what you get out of the preprocessor is simply an expression of the quality of what you put in.
Nesting
Looking at preprocessors it's very tempting to think that we can use them to create name spaces by nesting code, after all this looks a lot like block scope in curly brace languages.
.content-area {
div {
p {
&:first-of-type {
span {
}
}
}
}
}
However, just because it looks like something you are familiar with it doesn't mean that it actually works that way.
This is what that code turns into:
.content-area {}
.content-area div {}
.content-area div p {}
.content-area div p:first-of-type {}
.content-area div p:first-of-type span {}
The problem here is that we've ended up with a lot of specificity and a tight coupling to our markup which is going to be fragile and hard to maintain.
The important thing with preprocessors is to think about them in terms of the output, not the input. You're still writing CSS that will be delivered to the browser. There's no compiler doing any optimisations, you're just writing CSS indirectly.
Techniques
As the cascade-specificity bunfight situation is common across projects of any size (and using a preprocessor isn't going to magically make it go away) a number of techniques have evolved to deal with it.
SMACSS
Scalable and Modular Architecture for CSS
SMACSS is an approach that mitigates the worst of the cascade-specificity bunfight. The key principles of SMACSS are:
Structured code.
You should separate out your code into different areas of responsibility:
- reset/base/typography
- layout
- modules
Essentially we aren't mixing base styles and typography in with our layout and the presentation of individual modules of the UI. This is basic separation of concerns: we want to know where we should be making changes and, when we make those changes, we want to be confident that they're not going to have side effects.
Depth of applicability
If our CSS has too much depth of applicability then we can't re-use it elsewhere. For example, if we have a presentation that is tied into being the sidebar of our site we can't re-use it in the content area without piling on more selectors and more specificity.
Minimum specificity
The easiest way to avoid specificity issues is to always strive to use the minimum practical specificity, which is typically "one class".
When we're putting things together we're always looking for ways to use one class as our selector. Obviously that's not always going to be possible but, given the choice, it's better to have several "one class" selectors than one "several class" selector. It's generally easier to change classes in HTML than it is to refactor high specificity CSS.
OOCSS
Object Oriented CSS
OOCSS is a way of managing repetition within your CSS. Whilst you might not think it to be object orientated in the way you are used to from other languages it's a conceptually similar principle that has been proven on a lot of large CSS code bases.
With OOCSS we try and extract small re-usable elements - or objects - from our visual design. These objects should be really minimal so that we can combine them to form the basic UI framework of our sites.
OOCSS example
Here's an example of an element that we're using on our site - it's box with some content, an image on the left and a border.
We've split this out into a panel object that handles the layout and a border object that deals - surprisingly enough - with the border. Here we've combined these two but we can use this layout object elsewhere and apply different visual styles to it and we can apply this border object to other objects.
<div class="panel border">
<div class="content">
<p>…</p>
</div>
<div class="img">
<img src="…"/>
</div>
</div>
.panel { margin: 1em; clear:both; }
.panel .img { float:left; margin-right: 1em; }
.border { border: 1px solid grey; }
The key to OOCSS is to keep the objects small and re-usable. You're probably not going to have that many objects defined but they're going to form the basic scaffolding of your visual presentations.
BEM
Block, Element, Modifier
So we know we want to keep our specificity low - ideally one class - and we don't want to couple our CSS to our markup. OOCSS is good for providing the basics but we're going to run into its limits quite quickly - for more complex presentations we can't just keep piling on more objects, that's also going to be unmaintainable.
When we get into the detail of presentations we are going to use BEM: Block, Element, Modifier.
BEM syntax
BEM is how we get around the twin problem of a single name space and low specificity in CSS: we use a naming convention.
- Block
- Root of the component
- Separator
__
(Double underscore)- Element
- Individual part of the component
- Separator
--
(Double hyphen)- Modifier
- A variant or state
We break our code into logical blocks which will often match modular components in our UI. A block is the top level wrapping element of our module.
Because we're going to need to style different parts of our block and we want to keep the specificity low - so no nesting - we're going to use elements to refer to these individual parts. The class name of an element will consist of the block name followed by a double underscore and then the element name.
Finally, we might need to have different variants of a block or element and for this we use a modifier which is prefixed with double hyphen.
Here's what that looks like in practice:
.block {}
.block__element {}
.block__element--modifier {}
With the double underscores and hyphens this can look odd at first but by using the doubles it not only makes the parts stand out but we can use single underscores and hyphens in each part.
BEM in practice
Let's see what that looks like with some less abstract class names:
.read-more {}
.read-more__title {}
.read-more__content-expander {}
.read-more__content-expander--open {}
.read-more__content-expander--closed {}
These are some classes we might use for a "read more" presentation that has a title which, when you click on it, expands and collapses an content area.
At the top level we have our "block" - in this case called "read-more" - that's the wrapper round the component as a whole.
Within that we have "title" and "content expander" elements. It's probable that "title" is going to be used as the name of an element at several different places throughout our code base so if we'd just used .title
as a class name for those we're exposing ourselves to name space clashes. However, as the name of a block, .read-more
is going to be unique which means that .read-more__title
is also going to be unique.
Our content expander is going to need two states - 'open' and 'closed' - so this is where we use modifiers. Again, if we just used .open
and .closed
on their own as classes then we expose ourselves to name space clashes, this way we know that these will be unique.
The other benefit of this is that we have kept the specificity low: each of these is only one class. If we had done this by nesting then we'd have a specificity of three classes.
Using this methodology we're getting the dual benefits of avoiding conflicts in our single name space and keeping specificity low.
Combining OOCSS and BEM
OOCSS and BEM are complementary rather than contradictory techniques.
OOCSS is very good for removing repetition from our CSS but trying to build everything using reusable objects will lead to strings of classes in our HTML which is hard to reason about and has a high maintenance overhead.
BEM is very good for keeping specificty low whilst still allowing us to precisely target individual parts of the presentation but if we used it for every module of our presentation we would end up with a lot of repetition.
Consequently we use OOCSS to abstract away the repetition and BEM to manage the specificity when precisely targeting individual parts.
Combining the OOCSS and BEM examples given above we might use the following markup for a read more that has a border round it and uses text and images in the expander:
<div class="read-more bdr">
<div class="read-more__title">…</div>
<div class="read-more__content-expander panel">…</div>
</div>
If, at a later stage, we find that we want to re-use parts of the read more's title presentation elsewhere then we could refactor this out to a new object.
There's a balance to be struck between the increased complexity of using too many objects and the increased repetition caused by too much precision targeting. If you're in any doubt which way to go in a particular situation then it's generally better to chose the precision and accept the repetition rather than to prematurely optimise for re-use. If it subsequently becomes obvious that there is a clear case for re-use then you can refactor it out to an object as you now have a much better idea of what is being repeated.
JavaScript binding
Something that can create additional problems with CSS is JavaScript bindings. When binding functionality to the UI it can be tempting to use the existing, presentational, classes. The problem with this is that the functionality is now coupled to the presentation.
To avoid this we need to use dedicated hooks. Typically either prefixed classes or data attributes are used:
- Prefixed classes:
.js-…
- Data attributes:
[data-…]
Both of these approaches have their advantages and disadvantages so work out which one you are going to use and stick to it.
By using dedicated hooks to bind JavaScript means that you can move and re-use functionality without having to move and re-use the visual presentation. Taking the example of the read more expander we might have tied the expand-collapse functionality to the title but that would mean if we wanted to add an additional trigger then we'd be refactoring both the CSS and the JavaScript but if we had a dedicated hook we can just add another one into the content and we're done.
Specificity graph
Specificity graphs are a technique for reasoning about your CSS. By plotting specificity against source order we are effectively visualising the cascade and specificity at the same time.
The key to the specificity graph is that it trends upwards:
It's going to fluctuate in detail but the trend should be upwards:
Structuring your CSS in such a way that the specificity increases as you go through the code won't necessarily change how it works but the important thing is that it's easier to understand the intention of the code and it makes it easier to maintain as you can see where to place rules in your code. What we're really trying to avoid here is just adding new CSS onto the bottom each time as the code base grows because, as we've seen, that is a really good way to make your code hard to maintain.
CSS
- Cascade
- Specificity
- Single name space
The key to tolerating CSS is to understand how the cascade and specificity interact within the single name space. Once you understand how these three effect each other you can write your code in a more consistent, understandable and - ultimately - maintainable fashion.