The Cascade & Inheritance
As your project grows with an increasing number and variety of visual components (and their elements within the DOM), so too will your stylesheets grow with more rules to style them.
Recall the last example shown in the last chapter, where we had an element
<div class="smallText">Lorem ipsum dolor sit amet…</div>
and some styles
div {
color: green;
}
*.smallText {
font-size: 10px;
}
The <div>
element, we said, has text that is both green and 10 px in size,
because the rules from multiple rulesets with matching selectors are applied
cumulatively. What would happen to the element, though, if we were to update the
latter ruleset as thus?
div {
color: green;
}
*.smallText {
color: blue;
font-size: 10px;
}
You might (correctly) guess that its text size will still be 10 px, but the
color
properties now collide with different values. What color will be applied
to the text? Green? Blue? Some combination of green and blue?
Luckily, the language defines a conflict-resolution mechanism that allows us to answer this question. The eponymous Cascade is an integral concept in CSS, defining a way to deterministically choose which styles are applied to elements when they are targeted by many, possibly conflicting, rules. The workings of the cascade can seem mysterious, even for experienced front-end developers, so don’t be afraid to occasionally re-visit this chapter from time to time as you become acquainted with the platform.
The cascade is comprised of three main concepts which determine the order in which rules are applied: Importance, Specificity, and Source Order. Luckily, the first and last of these are pretty simple to grasp. Keep in mind that this competition of conflicting values, and the selection of which one will ultimately “win” and be applied to the element, happens at the level of a single rule. Thus, only a subset of the rules from a single ruleset may be applied to a given element.
Importance
Roughly, a rule’s calculated importance depends on by whom the rule was specified. From lowest to highest importance:
- The user agent default stylesheet (the browser’s default styles)
- Normal (non-important) rules by the author
- Normal rules by the user
- Important rules by the author (rule declarations with an
!important
flag) - Important rules by the user
An important rule is one that is defined with an !important
flag, which
appears at the very end of the rule definition before the ending semicolon.
div {
color: green !important;
}
and on inline styles:
<div style="color: green !important;">…</div>
Conversely, a normal rule is simply a non-important rule.
For any conflicting rules, the one with the highest importance will be applied.
Thus, rules that we define with an !important
flag are a quite powerful yet
blunt instrument as they are difficult to override. Marking rules as important
should generally only be used as a last resort of overriding other rules in the
cascade, and having many important rules is a good indicator that your styles
are probably in need of refactoring overall.
If two or more conflicting rules are of the same importance – which is the case when our own author rules are competing amongst themselves – we move on to the next metric.
Specificity
Specificity is the most complex, and sometimes non-intuitive, level of prioritizing conflicting rules. A rule’s specificity has four components, which are determined by its selector. For any conflicting rules, we compare their component values in order from most to least distinguishing. At any of these comparisons, if one rule’s value is higher than that of the other rules, then it will win.
The components of specificity, in order from most to least distinguishing, are:
- Inline – 1 for inline style attributes on the element, otherwise 0 (embedded or external styles).
- The number of id selectors in the selector.
- The number of attribute, class, and pseudo-class selectors in the selector.
- The number of element (type) and pseudo-element selectors in the selector.
Along with those rules, keep the following notes in mind:
- The universal selector (
*
) does not affect specificity. - Combinators (like
> , +
) do not affect specificity. - Recall that the
:not()
pseudo-class takes a simple selector as its argument. The argument contributes to the calculation of specificity as normal according to its selector type, but the:not()
pseudo-class itself does not count towards specificity.
Notice that inline styles have the highest specificity and thus will always
override any other styles of the same importance. This makes sense; applying a
style directly on an element itself is as specific as you can get. However, like
the !important
flag, inline styles should be used sparingly. Being separate
from the rest of your stylesheets, they can be more difficult to maintain and
prone to cause regression errors.
Source Order
Finally, if two conflicting rules have both the same importance and the same specificity, the tie is broken by source order. Whichever rule is defined last in the source code wins.
For this reason, I recommend that in the head
of your document you link
any
third-party stylesheets (such as those from UI libraries or frameworks)
before your own. That way, your own styles will effectively come later in
the source order. If any of your own rules conflict with those from third
parties, and they have both the same importance and the same specificity, then
yours will win.
Armed with our knowledge of the cascade, let us revisit the example from earlier
and answer the question: will the text on the element <div class="smallText">
be green or blue?
div {
color: green;
/*
importance: 5
specificity: 0 0 0 1
*/
}
*.smallText {
color: blue;
font-size: 10px;
/*
importance: 5
specificity: 0 0 1 0
*/
}
Let’s walk through the cascade: Importance. Specificity. Source Order.
All rules here are of the same importance – they are normal author rules – so we move on to compare the components of their specificity. As shown in the tally above, the latter rule has a higher specificity, therefore it wins – our element will indeed have blue text. (Had both selectors had the same specificity, we would have moved on to comparing source order, in which case the latter rule would have won again simply by virtue of being the latter rule.)
Inheritance
Much like in genetics, inheritance is the mechanism by which style property values given to elements are “passed down” to their descendants. Like the cascade, inheritance happens individually per style property.
An inherited property is one for which, if no other value is directly assigned, the value will be inherited from its parent’s computed value for that property.
The most obvious inherited properties are many related to text, such as color
and font-size
. For example, with the following elements:
<style>
div { color: blue; }
</style>
<div>Lorem <span>ipsum<span> dolor sit amet.</div>
All of the text inside of the <div>
element will be blue. Notice that the word
“ipsum” is contained within a <span>
element which is a child of the <div>
element. Even though this <span>
element is not targeted by the selector, it
will inherit the value for color
from its parent element because its own
color
value is not otherwise set.
This inheritance mechanism is a recursive function; that is, once an inherited
property is set on a given element, its value will be inherited by default for
all of the elements comprising the subtree for which the given element is the
root. (For example, if the document’s root element has set color: blue;
, then
all of the text on the page will be blue by default, no matter the document
structure or how deeply the contained elements are nested.)
Whether or not a property is inherited is defined by the spec, and – for the most part – follows common sense. For example, if I apply a font style to an element, it is likely that I want everything in that element to have that font style. (It would be quite annoying to have to explicitly set the same font-size for every nested element comprising a given UI component.)
A style property that does not inherit its value by default is called non-inherited. For example, box sizing properties (like width, height, padding, border, and margin) are non-inherited. This makes sense, too – if I apply one of these sizing properties to an element, it is unlikely that I want all of its descendants to automatically have the same sizing values themselves.
If an element’s non-inherited property is not set through any styles, its value defaults to the property’s initial value. The initial value for any given property is defined in the CSS spec.
Any style property – whether inherited or non-inherited – can be explicitly
directed to inherit its value using the value keyword inherit
. For example,
even though the margin
property is non-inherited, we can set it to inherit:
#someId {
margin: 1em;
}
#someId > * {
margin: inherit;
}
For the above styles, a given element with id someId
will have a margin of
1 em and all of its children will also have a margin of 1 em. However, the
grandchildren (and further non-child descendants) of #someId
will not
inherit this margin because margin is a non-inherited property.
Conversely, the value keyword initial
can be used to prevent an inherited
property from inheriting its value from its parent. Building upon our previous
example,
<style>
div { color: blue; }
div span { color: initial; }
</style>
<div>Lorem <span>ipsum<span> dolor sit amet.</div>
Now, the word “ipsum” inside of the <span>
element is shown in black text
because black
is the initial value for the color
property. (The rest of the
text inside of the <div>
element is still blue.)