Dueling Declarations: Following the Cascade
I originally wrote this article sometime back in 2003 for a site called the Nemesis Project that has since fallen off the Net. Because it still (shockingly) has some useful fundamental information I decided to republish it here just so it would have a home somewhere. I’ve done little more than reformat it slightly. If any information is out of date or if any links are broken feel free to leave a comment.
Following on the tail of my previous article on CSS 2 selectors, this time we’re going to examine what happens when rules collide. When two or more different rule sets select the same element in the document tree and have declarations that try to set the same property, how does the browser know which one to apply? Which declaration will override the others? These are the questions that put the “cascade” in Cascading Style Sheets.
In a nutshell, the browser has a set of criteria that it “cascades” down in order to determine which declaration is the winner. Obviously you need to know what those criteria are in order to understand the cascade, so let’s start by looking at the first one.
Origin & Weight
The first step in the cascade is really two things, but they’re both considered together so we’ll count them as one. The first half of this step is knowing where the declaration came from, and there are effectively three different involved parties that can each specify their own rules:
- The author
- This is probably the kind of declaration you’re most familiar with. These are what you’re creating when you write a style sheet to go with a document you’re publishing.
- The user
- You might also have some experience with user rules. Many browsers, now, allow the user to create a style sheet or sheets that are applied to any document he or she views. That’s where this type of declaration comes from.
- The user agent
- Whether you realize it or not, you use this kind of declaration all the time. Your browser has certain default styles that it applies to elements, and these need to be considered along with any set by the author or user.
Knowing which of those three defined each declaration makes up the “origin” part of this step. To make up the other half, there are two possible weights that a declaration can have:
- Important
- These are declarations that have been set with the
!important
flag. Only author and user style sheets may use the important flag. - Normal
- Simply put, these are any declarations that have not been flagged as important.
So, when a browser compares two or more declarations it first looks at their origin and weight, and if there is a clear winner it stops there. This is the order, from weakest to strongest, of the possible origin/weight combinations:
- Normal user agent declaration
- Normal user declaration
- Normal author declaration
- Important author declaration
- Important user declaration
As you can see, normal author declarations will override both normal user and normal user agent declarations, but important user declarations trump everything. To take a look at a quick example, if the following rule set were in a user style sheet:
h1 {
color: blue !important;
font-size: 2em;
}
And if this one were in an author style sheet:
h1 {
color: red !important;
font-size: 1.5em;
}
When applied to a document containing a first-level heading, the user’s color
declaration would win because it’s flagged as important, while the author’s
font-size
declaration would override the user’s. So, you’d end up
with a first-level heading that was 1.5em and blue.
If, after comparing origins and weights there’s still no clear winner (i.e., the two or more strongest declarations have the same origin and weight) the browser moves on to the next criterion with only those declarations still tied for the lead.
Specificity
In this step the browser compares the selectors of the rule sets the declarations came from. The specificity of a selector is frequently represented by a trio of values. These values, this time in order from strongest to weakest, are the numbers of:
- id selectors in the full selector
- class, other attribute, or pseudo-class selectors in the full selector
- element and pseudo-element selectors in the full selector
That is, an id selector is said to be more specific than a class or pseudo-class selector, which is more specific than an element or pseudo-element selector. The order of the various selector components is not important when calculating the selector’s specificity So, for example, this selector could be said to have a specificity of 0,1,1 (no id selectors, one class selector, and one element selector):
.sidebar p
In order to compare it to another selector you would look at their values from left to right and stop as soon as one of the selectors was clearly more specific than the other. For example, this selector’s specificity is 1,0,1 (one id selector; no class, attribute, or pseudo-class selectors; one element selector):
#footer p
In this case we see which selector is more specific right away—the second selector wins right at the first step, since its one id selector beats the none of the first selector. But, now let’s compare it against this selector:
div.sidebar p
It has one class selector and two element selectors, so its specificity is 0,1,2. Comparing this selector against the first one, the first two values are the same, so it’s not until the third value, the number of element selectors, that we see that this new selector is more specific.
I want to point out a couple of caveats here before I go any further. First, for the purposes of this step in the cascade, declarations from (X)HTML style attributes are considered to be more specific than any other declarations. The CSS 2.1 Spec introduces this as a new first value in what becomes the specificity quartet (a great name for a group of barbershop-singing Web developers, if there are any out there looking for a name). So, for example, you may see the specificity values of the above selectors written as 0,0,1,1; 0,1,0,1; and 0,1,0,2 respectively. Under that scheme, style attribute declarations always have a specificity value of 1,0,0,0.
Second, the counting of pseudo-elements in the third specificity value is a change from CSS 2, where they were not counted at all, to CSS 2.1. Even though CSS 2.1 is currently still a Working Draft, it represents how browsers have actually implemented the cascade, which really makes more sense. For example, if pseudo-elements weren’t counted, the following selectors would have the same specificity and, therefore, would need to be specified in a particular order (the last criterion in the cascade) in order for their declarations to be applied in the desired manner:
p { font-size: 1em; }
p:first-line { font-size: 2em; }
With those out of the way and because specificity is one of the areas of CSS that most frequently gives people acid indigestion, let’s take a look at a few more examples. If you wanted all of the links on a page to appear in red except for those in your navigation bar, a div conveniently given the id value “navigation,” which you want to be white, you might write two rule sets like these in your style sheet:
a { color: red; }
#navigation a { color: white; }
Now that you know how to calculate the specificity values of these two selectors you can see why the second will override the first where the two overlap: 0,0,1 < 1,0,1.
Here’s a very common mistake involving specificity. Take the following markup snippet:
<div id="main">
<p>This is a paragraph.</p>
<p>This is another paragraph.</p>
<p id="specialp">This is yet another paragraph.</p>
</div>
Now, if you wanted to style the third paragraph you might naturally create a
rule set with the selector #specialp
, and that would usually work
just fine. However, if you had somewhere else in any of your style sheets
created a rule set with the selector #main p
you might be surprised
to find its declarations overriding some or all of the declarations in your
#specialp
rule set. This is because, even though you might think
the selector #specialp
is more specific than #main p
—after all,
it can only apply to that one element in the entire document, while the other
rule set could apply to any number of paragraphs inside of the #main
div—its specificity score is actually lower than the other’s (1,0,0 <
1,0,1) which is why its declarations get overridden by the other’s.
Sometimes, these types of problems can be annoyingly subtle. Let’s say, for example, these were the rule sets you had attached to that markup snippet:
#main p { font: 1.2em sans-serif; }
#specialp { font-style: italic; }
You might spend quite a while staring at your page wondering why that third
paragraph wasn’t appearing in italics, until you realized that the font
shorthand property actually does collide with all of its component properties,
which includes font-style
. Specifically, not specifying a
font-style
value in the font
property is the same as
explicitly setting it to its initial value of normal
. Therefore,
because the two declarations collide we need to look to the cascade for their
resolution. Their origins and weights are the same, but the first selector is
more specific than the second. So the implicit font-style
setting
of normal
in the first rule set overrides the explicit
italic
value set in the second.
Just like when we fell from the preceding step, if the browser compares the selector specificities of the declarations its trying to resolve and there’s still no clear winner, it moves on to the next and final criterion in the cascade with the declarations still vying for dominance. There can be only one.
Order
If all else fails, the final, sure-fire way to determine which declaration will win is to look at the order in which they are specified. CSS is not a first-come-first-served technology—the last declaration made is the one to be applied.
There really isn’t much of a trick to this step. The easiest thing to do is to just mentally roll all your style sheets up into one big, long one. If you have a link element that references a style sheet in your document followed by a style element with rule sets of its own, you can think of them as a single style sheet with the rules from the external file appearing first, and those from the style element second. In this case, if there were a collision between two declarations from rule sets with the same origin and weight, and the same specificity, but one was in the external style sheet and the other was in the style element, the second would override the first because it’s specified later.
A rather important side note to this: In writing this article I discovered what seems to be a rather surprising bug in the cascade implementations of both Internet Explorer and Opera (and perhaps others). Take for example the following markup snippet:
<div id="one">
<div id="two">
If this is green your browser is cascading properly.
</div>
</div>
And apply to it these two CSS rule sets in a single author style sheet:
#one div { color: red; }
div #two { color: green; }
Now, we can see that both rules select the inner div and attempt to set its
color
property, so we know the cascade is going to come into play.
Following the rules of the cascade ourselves we look first at the declarations’
origins and weights and see that they are the same (normal author declaration).
So, we move on to their selectors’ specificities, and again, we see that they’re
the same (1,0,1). So finally we use the order in which they were specified to
determine what color the text in the inner div should be. Doing that we see that
it should be green, since that’s the declaration that’s made last.
However, if you look at it in Internet Explorer or Opera, you might be surprised with the results. For some reason, those browsers fail to apply the second declaration and instead color the text in the inner div red. No one that I’ve talked to yet has had any ideas for exactly what’s going on there, or why those browsers would fail on so simple an example. In fact, most of the people I mentioned it to were as surprised as I was.
The only thing I was able to determine is that it seems to have something to do with the second selector’s ending with a lone id selector, which for whatever reason causes its specificity to be counted incorrectly. The second rule, by itself, does style the text as green, so I know it’s not a problem with that selector matching the right element. It’s just that it’s being overridden for some reason by the rule before it. Adding element selectors to the two id selectors magically makes everything work just as it should, even though nothing’s really changed:
div#one div
div div#two
Their origins and weights are still the same (normal author declaration); their specificities, although different than before, are still the same compared to each other (1,0,2); and their order obviously is still the same. A very odd bug indeed.
Conclusion
You should now be able to cascade through your style sheets’ various declarations as well as (if not better than) your browser does. Understanding the cascade is really one of the most important pieces to truly “getting” CSS. It will not only help you to solve styling problems more efficiently by knowing when and where you can override some declarations and let others fall through, but it will also make you better able to debug those unforeseen problems that will invariably arise.