Mastering CSS: Understanding the Cascade
Reading time
~ 7 min read
Tags
css
Author
Mustapha Aouas
Cascading Style Sheets (CSS) is a fundamental technology of the web, allowing developers to control the visual presentation of HTML documents. While CSS syntax may seem simple at first glance, the way styles are applied and inherited can be surprisingly complex. Understanding these intricacies is crucial for writing efficient, maintainable, and predictable CSS. In this comprehensive guide, we'll explore the cascade and inheritance concepts of CSS.
The CSS Cascade
The cascade is the algorithm that determines which CSS rules are applied to elements when multiple conflicting rules exist. It's essential to understand how the cascade works to write CSS that behaves as expected. The cascade considers several factors in the following order:
- 1 Stylesheet origin
- 2 Inline styles
- 3 Selector specificity
- 4 Source order
To be completely exhaustive, we can add:
Let's break down the factors that influence the cascade, in order of precedence:
1. Stylesheet Origin
CSS can come from three different sources:
- 1 User-agent styles: These are the browser's default styles. Each browser has its own set of default styles, which is why unstyled HTML can look slightly different across browsers.
- 2 User styles: These are custom styles set by the user. While rare, some users may have custom stylesheets to override default styles for accessibility or personal preference.
- 3 Author styles: These are the styles you write as a web developer.
Generally, author styles take precedence over user styles, which in turn override user-agent styles. This allows developers to customise the appearance of elements while still respecting user preferences when necessary.
2. Inline Styles
Styles applied directly to an element using the style attribute have very high priority:
<p style="color: red;">This text will be red.</p>
Inline styles will override any styles defined in external stylesheets or <style>
tags, regardless of their specificity (That’s no longer true if you use the !important
keyword. I’ll get on that in a second).
Using inline styles is generally discouraged as it mixes presentation with content and makes styles harder to maintain.
3. Selector Specificity
Specificity is a crucial concept in CSS that determines which styles are applied to an element when multiple conflicting rules exist. Each CSS selector has a specificity number, which can be calculated to predict which styles will take precedence.
Specificity is typically represented as a four-part number (a,b,c,d), where:
- a: Number of inline styles (generally omitted)
- b: Number of ID selectors
- c: Number of class selectors, attribute selectors, and pseudo-classes
- d: Number of element selectors and pseudo-elements
The resulting number is not base 10. Instead, think of it as separate columns that are compared from left to right. See the examples:
p
= (0,0,0,1).class
= (0,0,1,0)#id
= (0,1,0,0)- Inline style = (1,0,0,0)
Consider these two conflicting rules:
#header .nav li { color: blue; } /* (0,1,1,1) */
nav > li a { color: red; } /* (0,0,0,3) */
The first rule (0,1,1,1) has higher specificity, so the text would be blue.
Pseudo-class selectors (such as
:hover
) and attribute selectors (such as[type="text"]
) each have the same specificity as class selectors.The universal selector (
*
) and combinators (>
,+
,~
) do not affect specificity.Also, the
:not()
pseudo-class also doesn't add to the specificity value; only the selectors inside it are counted.
Several online tools can help calculate specificity (https://specificity.keegan.st/).
4. Source Order
If all else is equal, the rule that appears later in the stylesheet takes precedence:
button { background-color: blue; }
button { background-color: green; } /* This one wins */
In this example, buttons will have a green background.
A Powerful Override
While understanding the cascade is crucial for writing maintainable CSS,
there's one more piece of the puzzle that can override all the rules
we've discussed so far: the !important
keyword.
How !important
Works
The !important
keyword can override all other considerations in the cascade, except for other !important
declarations of higher origin precedence.
/* styles.css */
button {
background-color: blue !important;
}
<!-- index.html -->
<head>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<button style="background-color: red"> My button </button>
<!-- The color will be blue due to !important above -->
</body>
In this example, even though inline styles usually have the highest priority, the button will still have a blue background because of the !important
declaration.
The Cascade and !important
The !important
keyword actually introduces additional layers to the cascade. The full order of precedence, from highest to lowest, is:
- User agent !important declarations
- User !important declarations
- Author !important declarations
- Important Inline styles
- Important not inlined styles
- Author normal declarations
- Inline styles
- Not inlined styles
- User normal declarations
- User agent normal declarations
When to Use it
While !important
can be tempting as a quick fix, it's generally considered a last resort. Overuse can lead to specificity wars and make your CSS harder to maintain. Legitimate use cases include:
- Overriding third-party styles you can't modify
- Creating utility classes that should always apply
- Ensuring critical accessibility styles are applied
A Potential Solution To Simplify Specificity Management
If you find yourself using !important often, consider refactoring your CSS to use more specific selectors or a more modern approach like utilising :is()
and :where()
to write more flexible and maintainable styles. (I talk about these two in more details here)
Also, the @layer
at-rule, which is fairly supported, allows you to create "layers" of styles with explicitly defined order of precedence:
@layer base, components, utilities;
@layer utilities {
.btn { padding: 10px 20px; }
}
@layer components {
.btn { padding: 1rem 2rem; }
}
This offers a more structured approach to managing style precedence without resorting to !important
or engaging in a specificity arms race. However, I haven’t used this in a production project myself, if you do, I’d love to hear about your experience :)
Inheritance
Passing Styles Down the DOM Tree
Inheritance is another fundamental concept in CSS. Some CSS properties are inherited by default, meaning child elements will take on the computed values of their parents. This is particularly useful for text-related properties like color
, font
, font-family
, font- size
, font-weight
, font-variant
, font-style
, line-height
, letter-spacing
, text-align
, text-indent
, text-transform
, white-space
, and word-spacing
.
body {
font-family: Arial, sans-serif;
color: #333;
line-height: 1.5;
}
In this example, all text within the body will inherit these styles unless explicitly overridden. This allows for efficient styling of document-wide typography without having to repeat rules for every element.
A few others inherit as well, such as the list properties:
list-style
,list-style-type
,list-style-position
,list-style-image
, and some other table related properties
Not all properties are inherited by default. For example, border and padding are not inherited, which makes sense – you wouldn't want every child element to automatically have the same border as its parent.
Inheritance keywords
CSS provides several keywords to give you fine-grained control over inheritance and to reset styles:
- The
inherit
keyword forces a property to inherit its value from its parent element (This can be useful for properties that don't inherit by default, like border in this example). - The
initial
keyword resets a property to its initial value as defined by the CSS specification (This can be helpful when you want to completely reset an element's styling). - The
unset
keyword acts like inherit for inherited properties and initial for non-inherited properties (This provides a flexible way to reset properties without needing to know whether they're inherited or not). - The
revert
keyword resets the property to the value it would have had if no author styles were applied (This is useful when you want to fall back to browser defaults rather than CSS-defined initial values).
The initial
and unset
keywords override all styles, affecting both author and user-agent stylesheets. This means they reset the element's styling to its default state, ignoring any previous styling rules applied by the author or the browser.
However, there are scenarios where you only want to reset the styles you’ve defined in your author stylesheet, without disturbing the default styles provided by the browser (user-agent stylesheet). In such cases, the revert keyword is particularly useful. It specifically reverts the styles of an element back to the browser’s default styles, effectively undoing any custom author-defined styles while preserving the inherent browser styling.
Note that when using shorthand properties omitted values are implicitly set to their initial values. This can potentially override other styles you've set elsewhere.
Wrapping up
By understanding the intricacies of the cascade, inheritance, and modern CSS features, you'll be better equipped to write efficient and maintainable stylesheets.
Remember, CSS is not just about making things look good – it's about creating robust, flexible designs that work across a wide range of devices and browsers.