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.