Introducing the CSS Cascade
The cascade is an algorithm that defines how user agents combine property values originating from different sources. The cascade defines the origin and layer that takes precedence when declarations in more than one origin, cascade layer, or @scope
block set a value for a property on an element.
The cascade lies at the core of CSS, as emphasized by the name: Cascading Style Sheets. When a selector matches an element, the property value from the origin with the highest precedence gets applied, even if the selector from a lower precedence origin or layer has greater specificity.
This article explains what the cascade is and the order in which CSS declarations cascade, covering cascade layers and origin type. Understanding origin precedence is key to understanding the cascade.
Origin types
The CSS cascade algorithm's job is to select CSS declarations in order to determine the correct values for CSS properties. CSS declarations come from different origin types: User-agent stylesheets, Author stylesheets, and User stylesheets.
Though style sheets come from these different origins and can be within different layers in each of these origins, they overlap in terms of their default scope; to make this work, the cascade algorithm defines how they interact. Before addressing the interactions, we'll define some key terms in the next few sections.
User-agent stylesheets
User-agents, or browsers, have basic style sheets that give default styles to any document. These style sheets are named user-agent stylesheets. Most browsers use actual stylesheets for this purpose, while others simulate them in code. The end result is the same.
Some browsers let users modify the user-agent stylesheet, but this is rare and not something that can be controlled.
Although some constraints on user-agent stylesheets are set by the HTML specification, browsers have a lot of latitude: that means some differences exist between browsers. To simplify the development process, Web developers may use a CSS reset stylesheet, such as normalize.css, which sets common properties values to a known state for all browsers before beginning to make alterations to suit their specific needs.
Unless the user-agent stylesheet includes an !important
next to a property, making it "important", styles declared by author styles, including a reset style sheet, take precedence over the user-agent styles, regardless of the specificity of the associated selector.
Author stylesheets
Author stylesheets are the most common type of style sheet; these are the styles written by web developers. These styles can reset user-agent styles, as noted above, and define the styles for the design of a given web page or application. The author, or web developer, defines the styles for the document using one or more linked or imported stylesheets, <style>
blocks, and inline styles defined with the style
attribute. These author styles define the look and feel of the website — its theme.
User stylesheets
In most browsers, the user (or reader) of the website can choose to override styles using a custom user stylesheet designed to tailor the experience to the user's wishes. Depending on the user agent, user styles can be configured directly or added via browser extensions.
Cascade layers
The cascade order is based on origin type. The cascade within each origin type is based on the declaration order of cascade layers within that type. For all origins - user-agent, author, or user - styles can be declared within or outside of named or anonymous layers. When declared using layer
, layer()
or @layer
, styles are placed into the specified named layer, or into an anonymous layer if no name is provided. Styles declared outside of a layer are treated as being part of an anonymous last declared layer.
Let's take a look at cascading origin type before diving into cascade layers within each origin type.
Cascading order
The cascading algorithm determines how to find the value to apply for each property for each document element. The following steps apply to the cascading algorithm:
- Relevance: It first filters all the rules from the different sources to keep only the rules that apply to a given element. That means rules whose selector matches the given element and which are part of an appropriate
media
at-rule. - Origin and importance: Then it sorts these rules according to their importance, that is, whether or not they are followed by
!important
, and by their origin. Ignoring layers for the moment, the cascade order is as follows:Order (low to high) Origin Importance 1 user-agent (browser) normal 2 user normal 3 author (developer) normal 4 CSS @keyframe animations 5 author (developer) !important
6 user !important
7 user-agent (browser) !important
8 CSS transitions - Specificity: In case of equality with an origin, the specificity of a rule is considered to choose one value or another. The specificity of the selectors are compared, and the declaration with the highest specificity wins.
- Scoping proximity: When two selectors in the origin layer with precedence have the same specificity, the property value within scoped rules with the smallest number of hops up the DOM hierarchy to the scope root wins. See How
@scope
conflicts are resolved for more details and an example. - Order of appearance: In the origin with precedence, if there are competing values for a property that are in style block matching selectors of equal specificity and scoping proximity, the last declaration in the style order is applied.
The cascade is in ascending order, meaning animations have precedence of normal values, whether those are declared in user, author, or user-agent styles, important values take precedence over animations, and transitions have precedence over important values.
Note: Transitions and animations
Property values set by animation @keyframes
are more important than all normal styles (those with no !important
set).
Property values being set in a transition
take precedence over all other values set, even those marked with !important
.
The cascade algorithm is applied before the specificity algorithm, meaning if :root p { color: red;}
is declared in the user stylesheet (row 2) and a less specific p {color: blue;}
is in the author stylesheet (row 3), the paragraphs will be blue.
Basic example
Before taking a deeper look at how cascade layers impact the cascade, let's look at an example involving multiple sources of CSS across the various origins, and work through the steps of the cascade algorithm:
Here we have a user agent style sheet, two author style sheets, and a user stylesheet, with no inline styles within the HTML:
User-agent CSS:
li {
margin-left: 10px;
}
Author CSS 1:
li {
margin-left: 0;
} /* This is a reset */
Author CSS 2:
@media screen {
li {
margin-left: 3px;
}
}
@media print {
li {
margin-left: 1px;
}
}
@layer namedLayer {
li {
margin-left: 5px;
}
}
User CSS:
.specific {
margin-left: 1em;
}
HTML:
<ul>
<li class="specific">1<sup>st</sup></li>
<li>2<sup>nd</sup></li>
</ul>
In this case, declarations inside li
and .specific
rules should apply.
Once again, there are five steps in the cascade algorithm, in order:
- Relevance
- Origin and importance
- Specificity
- Scoping proximity
- Order of appearance
The 1px
is for print media. Due to lack of relevance based on its media type, it is removed from consideration.
No declaration is marked as !important
, so the precedence order is author style sheets over user style sheets over user-agent stylesheet. Based on origin and importance, the 1em
from the user stylesheet and the 10px
from the user-agent stylesheet are removed from consideration.
Note that even though the user style on .specific
of 1em
has a higher specificity, it's a normal declaration in a user style sheet. As such, it has a lower precedence than any author styles, and gets removed by the origin and importance step of the algorithm before specificity even comes into play.
There are three declarations in author stylesheets:
li {
margin-left: 0;
} /* from author css 1 */
@media screen {
li {
margin-left: 3px;
}
}
@layer namedLayer {
li {
margin-left: 5px;
}
}
The last one, the 5px
is part of a cascade layer. Normal declarations in layers have lower precedence than normal styles not in a layer within the same origin type. This is also removed by step 2 of the algorithm, origin and importance.
This leaves the 0
and the 3px
, which both have the same selector, hence the same specificity. Neither of them are inside a @scope
block, so scoping proximity does not come into play in this example either.
We then look at order of appearance. The second one, the last of the two unlayered author styles, wins.
margin-left: 3px;
Note: The declaration defined in the user CSS, while it may have greater specificity, is not chosen as the cascade algorithm's origin and importance is applied before the specificity algorithm. The declaration defined in a cascade layer, though it may come later in the code, will not have precedence either as normal styles in cascade layers have less precedence than normal unlayered styles. Order of appearance only matters when both origin, importance, and specificity are equal.
Author styles: inline styles, layers, and precedence
The table in Cascading order provided a precedence order overview. The table summarized the user-agent, user, and author origin type styles in two lines each with "origin type - normal" and "origin type - !important". The precedence within each origin type is more nuanced. Styles can be contained within layers within their origin type, and, with author styles, there is also the issue of where inline styles land in the cascade order.
The order in which layers are declared is important in determining precedence. Normal styles in a layer take precedence over styles declared in prior layers; with normal styles declared outside of any layer taking precedence over normal layered styles regardless of specificity.
In this example, the author used CSS's @import
rule to import five external style sheets within a <style>
information element.
<style>
@import unlayeredStyles.css;
@import AStyles.css layer(A);
@import moreUnlayeredStyles.css;
@import BStyles.css layer(B);
@import CStyles.css layer(C);
p {
color: red;
padding: 1em !important;
}
</style>
and then in the body of the document we have inline styles:
<p style="line-height: 1.6em; text-decoration: overline !important;">Hello</p>
In the CSS code block above, three cascade layers named "A", "B", and "C", were created, in that order. Three stylesheets were imported directly into layers and two were imported without creating or being assigned to a layer.
The "All unlayered styles" in the list below (normal author style precedence - order 4) includes styles from these two stylesheets and the additional unlayered CSS style blocks. In addition, there are two inline styles, a normal line-height
declaration and an important text-decoration
declaration:
Order (low to high) | Author style | Importance |
---|---|---|
1 | A - first layer | normal |
2 | B - second layer | normal |
3 | C - last layer | normal |
4 | All unlayered styles | normal |
5 | inline style |
normal |
6 | animations | |
7 | All unlayered styles | !important |
8 | C - last layer | !important |
9 | B - second layer | !important |
10 | A - first layer | !important |
11 | inline style |
!important |
12 | transitions |
In all origin types, the non important styles contained in layers have the lowest precedence. In our example, the normal styles associated with the first declared layer (A) have lower precedence than normal styles in the second declared layer (B), which have lower precedence than normal styles in the third declared layer (C). These layered styles have lower precedence than all normal unlayered styles, which includes normal styles from unlayeredStyles.css
, moreUnlayeredStyles.css
, and the color
of p
in the <style>
itself.
If any of the layered styles in A, B, or C, have selectors with higher specificity matching an element, similar to :root body p { color: black;}
, it doesn't matter. Those declarations are removed from consideration because of origin; normal layered styles have less precedence than normal unlayered styles. If, however, the more specific selector :root body p { color: black;}
was found in unlayeredStyles.css
, as both origin and importance have the same precedence, specificity would mean the more specific, black declaration would win.
The layer order of precedence is inverted for styles declared as !important
. Important styles declared in a layer take precedence over important styles declared outside of a layer. Important styles in the first declared layer (A) take precedence over important declarations found in layer B, which takes precedence over C, which have precedence over important declarations in the unlayered styles.
Inline styles
Only relevant to author styles are inline styles, declared with the style
attribute. Normal inline styles take precedence over any other normal author styles, no matter the specificity of the selector. If line-height: 2;
were declared in a :root body p
selector block in any of the five imported stylesheets, the line height would still be 1.6
.
Normal inline styles take precedence over any other normal author styles unless the property is being altered by a CSS animation.
All important inline styles take precedence over all author styles, important and not, inline and not, layered and not. Important styles also take precedence over animated properties, but not transitioning properties. Three things can override an important inline style: 1) an important user style, 2) an important user agent style, or 3) a property value being transitioned.
Importance and layers
The origin type precedence order is inverted for important styles. Important styles declared outside of any cascade layer have lower precedence than those declared as part of a layer. Important values that come in early layers have precedence over important styles declared in subsequent cascade layers.
Take for example the following CSS:
p {
color: red;
}
@layer B {
:root p {
color: blue;
}
}
Even though the red is declared first and has a less specific selector, because unlayered CSS takes precedence over layered CSS, the paragraph will be red. Had we included an inline style on the paragraph setting it to a different color, such as <p style="color: black">
, the paragraph would be black.
When we add !important
to this bit of CSS, the precedence order is reversed with the stylesheet:
p {
color: red !important;
}
@layer B {
:root p {
color: blue !important;
}
}
Now the paragraph will be blue. The !important
in the earliest declared layer takes precedence of subsequent layers and unlayered important declarations. If the inline style contained !important, such as <p style="color: black !important">
, again the paragraph would be black. Inline importance does take precedence over all other author declared !important
declarations, no matter the specificity.
Note: The !important
flag reverses the precedence of cascade layers. For this reason, try not to use !important
to override external styles. Instead, use @import
together with the layer
keyword or layer()
function to import external style sheets (from frameworks, widget stylesheets, libraries, etc.) into layers. Importing stylesheets into a layer as the first declaration in your CSS demotes their precedence, and author-defined layers, defined later in your CSS, will have higher precedence. The !important
flag should only be used sparingly, if ever, to guard required styles against later overrides, in the first declared layer.
Styles that are transitioning take precedence over all important styles, no matter who or how they are declared.
Complete cascade order
Now that we have a better understanding of origin type and cascade layer precedence, we realize the table in Cascading order could have more accurately been represented by the following table:
Precedence Order (low to high) |
Style Origin | Importance |
---|---|---|
1 | user-agent - first declared layer | normal |
user-agent - last declared layer | ||
user-agent - unlayered styles | ||
2 | user - first declared layer | normal |
user - last declared layer | ||
user - unlayered styles | ||
3 | author - first declared layer | normal |
author - last declared layer | ||
author - unlayered styles | ||
inline style |
||
4 | animations | |
5 | author - unlayered styles | !important |
author - last declared layer | ||
author - first declared layer | ||
inline style |
||
6 | user - unlayered styles | !important |
user - last declared layer | ||
user - first declared layer | ||
7 | user-agent - unlayered styles | !important |
user-agent - last declared layer | ||
user-agent - first declared layer | ||
8 | transitions |
Which CSS entities participate in the cascade
Only CSS property/value pair declarations participate in the cascade. CSS at-rule descriptors don't participate in the cascade and HTML presentational attributes are not part of the cascade.
At-rules
CSS at-rules containing entities other than declarations, such as a @font-face
rule containing descriptors, don't participate in the cascade.
For the most part, the properties and descriptors defined in at-rules don't participate in the cascade. Only at-rules as a whole participate in the cascade. For example, within a @font-face
rule, font names are identified by font-family
descriptors. If several @font-face
rules with the same descriptor are defined, only the most appropriate @font-face
, as a whole, is considered. If more than one are identically appropriate, the entire @font-face
declarations are compared using steps 1, 2, and 4 of the algorithm (there is no specificity when it comes to at-rules).
While the declarations contained in most at-rules — such as those in @media
, @document
, or @supports
— participate in the cascade, the at-rule may make an entire selector not relevant, as we saw with the print style in the basic example.
Declarations in @keyframes
don't participate in the cascade. As with @font-face
, only the @keyframes
as a whole is selected via the cascade algorithm. The precedence order of animation is described below.
When it comes to @import
, the @import
doesn't participate itself in the cascade, but all of the imported styles do participate. If the @import
defines a named or anonymous layer, the contents of the imported stylesheet are placed into the specified layer. All other CSS imported with @import
is treated as the last declared layer. This was discussed above.
Finally, @charset
obeys specific algorithms and isn't affected by the cascade algorithm.
Presentational attributes
Presentational attributes are attributes in the source document that can affect styling. For example, when included, the deprecated align
attribute sets the alignment on several HTML elements and the fill
attribute defines the color used to paint SVG shapes and text and defines the final state for SVG animations. While they are author styles, presentational attributes do not participate in the cascade.
If the HTML presentation attribute is supported by the user agent, valid presentational attributes included in HTML and SVG, such as the align
or fill
attributes, are translated to the corresponding CSS rules (all SVG presentation attributes are supported as CSS properties) and inserted in the author stylesheet prior to any other styles with a specificity equal to 0
.
Presentational attributes cannot be declared !important
.
CSS animations and the cascade
CSS animations, using @keyframes
at-rules, define animations between states. Keyframes don't cascade, meaning that at any given time CSS takes values from only one single @keyframes
, and never mixes multiple ones together.
If the several keyframe animations are defined with the same animation name, the last defined @keyframes
in the origin and layer with the greatest precedence. Only one @keyframes
definition is used, even if the @keyframes
animate different property. @keyframes
with the same name are never combined.
p {
animation: infinite 5s alternate repeatedName;
}
@keyframes repeatedName {
from {
font-size: 1rem;
}
to {
font-size: 3rem;
}
}
@layer A {
@keyframes repeatedName {
from {
background-color: yellow;
}
to {
background-color: orange;
}
}
}
@layer B {
@keyframes repeatedName {
from {
color: white;
}
to {
color: black;
}
}
}
In this example, there are three separate animation declaration named repeatedName
. When animation: infinite 5s alternate repeatedName
is applied to the paragraph, only one animation is applied: the keyframe animation defined in the unlayered CSS takes precedence over the layered keyframe animation declarations based on origin and cascade layer precedence order. In this example, only the element's font size will be animated.
Note: There are no important animations, as property declarations in a @keyframes
block that contain !important
as part of the value are ignored.
Resetting styles
After your content has finished altering styles, it may find itself in a situation where it needs to restore them to a known state. This may happen in cases of animations, theme changes, and so forth. The CSS property all
lets you quickly set (almost) everything in CSS back to a known state.
all
lets you opt to immediately restore all properties to any of their initial (default) state, the state inherited from the previous level of the cascade, a specific origin (the user-agent stylesheet, the author stylesheet, or the user stylesheet), or even to clear the values of the properties entirely.
Specifications
Specification |
---|
CSS Cascading and Inheritance Level 4 |