We are still cooking the magic in the way!
Specificity, Cascade & Inheritance
Understanding the Cascade
CSS stands for Cascading Style Sheets, and the word "cascading" is the key to understanding how CSS decides which styles to apply when multiple rules target the same element. The cascade is the algorithm the browser uses to resolve conflicts between competing CSS declarations. When two or more rules try to set the same property on the same element, the cascade determines the winner.
The cascade evaluates declarations based on four criteria, in order of priority:
- Origin and Importance -- Where the style comes from and whether it is marked
!important. - Specificity -- How precisely the selector targets an element.
- Order of Appearance -- When two rules have equal specificity, the one that appears later wins.
- Inheritance -- Some properties are passed down from parent elements to children.
Origin of Stylesheets
Not all stylesheets are created equal. Browsers use styles from three different origins, and each origin has a different level of authority:
- User-Agent Stylesheet -- These are the browser's built-in default styles. For example, browsers display
<h1>elements in a large bold font and<a>elements in blue with an underline by default. Every browser has slightly different defaults, which is why developers often use CSS resets or normalize stylesheets. - User Stylesheet -- These are styles set by the user, often for accessibility reasons. A user might increase the base font size or force high-contrast colors. User stylesheets are rarely encountered in modern development but still exist in the cascade algorithm.
- Author Stylesheet -- These are the styles you write as a developer. Linked stylesheets, embedded
<style>blocks, and inline styles all fall under the author origin. Author styles override user-agent styles by default.
Under normal circumstances, the priority order from lowest to highest is: user-agent, user, author. However, when !important is used, this order is reversed for those declarations -- we will cover that in detail later.
Specificity: The Core of CSS Conflict Resolution
Specificity is a scoring system that determines which CSS rule takes precedence when multiple rules with the same origin target the same element and set the same property. Every selector has a specificity value that can be calculated. The rule with the higher specificity wins.
Specificity is calculated using four categories, often represented as a four-part number (a, b, c, d):
- Inline styles (a) -- An inline style attribute on an element has the highest specificity. It is scored as 1,0,0,0 (or simply 1000 in a flat calculation).
- ID selectors (b) -- Each ID selector (
#header,#main-nav) adds 0,1,0,0 (or 100) to the specificity score. - Class selectors, pseudo-classes, and attribute selectors (c) -- Each class (
.active), pseudo-class (:hover,:first-child), or attribute selector ([type="text"]) adds 0,0,1,0 (or 10) to the score. - Element selectors and pseudo-elements (d) -- Each element type (
div,p,h1) or pseudo-element (::before,::after) adds 0,0,0,1 (or 1) to the score.
*), combinators (+, ~, >, space), and the :where() pseudo-class have zero specificity. The :not() and :is() pseudo-classes do not add specificity themselves, but their arguments do count.Specificity Calculation Examples
Let us walk through several examples to make specificity calculation concrete. Understanding how to calculate specificity by hand will save you hours of debugging.
Example: Basic Specificity Scores
/* Specificity: 0,0,0,1 (one element selector) */
p {
color: black;
}
/* Specificity: 0,0,1,0 (one class selector) */
.intro {
color: blue;
}
/* Specificity: 0,1,0,0 (one ID selector) */
#main-title {
color: red;
}
/* Specificity: 1,0,0,0 (inline style) */
/* <p style="color: green;">This text is green</p> */In the example above, if a paragraph has the class intro and the ID main-title, and also has an inline style, the inline style wins because it has the highest specificity (1,0,0,0). Remove the inline style, and the ID selector wins. Remove the ID, and the class wins. Remove the class, and the element selector applies.
Example: Compound Selectors and Their Specificity
/* Specificity: 0,0,1,1 (one class + one element) */
p.highlight {
color: orange;
}
/* Specificity: 0,0,2,1 (two classes + one element) */
p.highlight.active {
color: yellow;
}
/* Specificity: 0,1,1,1 (one ID + one class + one element) */
div#content .intro {
color: purple;
}
/* Specificity: 0,1,2,2 (one ID + two classes + two elements) */
div#sidebar ul.nav-list .item {
color: teal;
}
/* Specificity: 0,0,1,3 (one class + three elements) */
header nav ul.main-menu {
color: brown;
}To calculate the specificity of div#sidebar ul.nav-list .item: count the IDs (one: #sidebar) for the second column, count the classes (two: .nav-list and .item) for the third column, and count the element selectors (two: div and ul) for the fourth column. The result is 0,1,2,2.
Example: Pseudo-classes and Attribute Selectors in Specificity
/* Specificity: 0,0,1,1 (one pseudo-class + one element) */
a:hover {
color: red;
}
/* Specificity: 0,0,2,1 (one pseudo-class + one attribute selector + one element) */
input[type="email"]:focus {
border-color: blue;
}
/* Specificity: 0,0,1,2 (one pseudo-element + one class) */
/* Note: ::before counts as an element-level selector */
.card::before {
content: "";
}
/* Specificity: 0,1,1,1 (one ID + one pseudo-class + one element) */
#nav a:visited {
color: gray;
}The !important Declaration
The !important annotation is a way to override all normal specificity rules. When you add !important to a declaration, it moves that declaration to a special high-priority layer that beats everything except other !important declarations.
Example: Using !important
/* Normal declaration -- specificity 0,1,0,0 */
#header {
background-color: white;
}
/* This wins even though the specificity is lower (0,0,1,0) */
/* because !important overrides normal specificity */
.dark-theme {
background-color: black !important;
}
/* To override an !important, you need another !important
with equal or higher specificity */
#header.dark-theme {
background-color: #1a1a1a !important;
}When multiple !important declarations compete, the normal specificity rules apply among them. This creates an escalation problem -- once you start using !important, you often need more !important declarations to override the first ones, leading to unmaintainable CSS.
!important in your regular stylesheets. It breaks the natural cascade flow and makes debugging significantly harder. Legitimate uses for !important are rare and include: utility classes in CSS frameworks (like .hidden { display: none !important; }), overriding third-party widget styles you cannot modify directly, and temporary debugging during development (always remove it afterward). If you find yourself reaching for !important, it is almost always a sign that your selector structure needs refactoring.Order of Appearance
When two declarations have the same origin, the same importance (both normal or both !important), and the same specificity, the cascade falls back to order of appearance. The declaration that appears later in the source code wins.
Example: Order of Appearance Resolving Ties
/* Both have specificity 0,0,1,0 */
.message {
color: blue;
}
/* This one wins because it appears later */
.message {
color: green;
}
/* The same principle applies across stylesheets.
If styles.css is linked before theme.css in HTML,
then theme.css declarations win when specificity ties. */
/* In HTML head: */
/* <link rel="stylesheet" href="styles.css"> */
/* <link rel="stylesheet" href="theme.css"> */This is why CSS resets and normalize stylesheets are always loaded first -- your custom styles come after and naturally override the resets when specificity is equal. This principle is also why you should structure your stylesheets carefully: base and reset styles first, then layout styles, then component styles, and finally utility or override styles. Each layer builds upon the previous one, and later rules naturally take precedence when specificity is the same.
How the Full Cascade Algorithm Works Step by Step
When the browser encounters a conflict between two CSS declarations targeting the same property on the same element, it resolves the conflict by walking through these steps in order. The first step that produces a clear winner ends the process:
- Step 1: Compare origins and importance. The browser checks where each declaration comes from (user-agent, user, or author) and whether either is marked
!important. Normal author styles beat normal user styles, which beat normal user-agent styles. But!importantreverses this order:!importantuser-agent styles beat!importantuser styles, which beat!importantauthor styles. If both declarations come from the same origin and have the same importance level, move to Step 2. - Step 2: Compare specificity. The browser calculates the specificity of each selector using the (a, b, c, d) system described above. The declaration with the higher specificity value wins. If both have equal specificity, move to Step 3.
- Step 3: Compare order of appearance. The declaration that appears later in the source order wins. This includes the order of linked stylesheets in the HTML
<head>, the position within a single stylesheet, and whether the style is in an external file, a<style>block, or an inline style attribute.
Understanding this algorithm completely means you can predict exactly which style will be applied in any situation. This is what separates developers who "guess and check" with CSS from those who write styles with confidence.
Common Specificity Pitfalls and How to Avoid Them
Even experienced developers fall into specificity traps. Here are the most common problems and their solutions:
Pitfall 1: Over-qualifying selectors. Writing div#header instead of just #header adds unnecessary specificity. The ID is already unique -- adding the element type in front of it makes it harder to override later without escalating specificity further. The same applies to a.nav-link when .nav-link alone is sufficient.
Pitfall 2: Deep nesting. Writing selectors like .header .nav .nav-list .nav-item .nav-link creates a specificity of 0,0,5,0 when a single class like .nav-link (0,0,1,0) would target the same element. Deep nesting makes your CSS brittle and hard to override. Every additional class in the chain raises the bar for any future rule that needs to override it.
Pitfall 3: Using IDs for styling. ID selectors have a specificity of 100, which is ten times higher than a class selector. A single ID selector beats any combination of class selectors (up to 10 classes). This makes ID-based styles very hard to override without resorting to more IDs or !important. Best practice is to reserve IDs for JavaScript hooks and use classes exclusively for styling.
Pitfall 4: Inline styles from JavaScript. When JavaScript adds styles via element.style.property = value, it creates inline styles with a specificity of 1000. These can only be overridden by other inline styles or by !important in a stylesheet. If you need to dynamically style elements, consider toggling CSS classes with JavaScript instead of setting inline styles directly.
Inheritance: Passing Styles Down the DOM Tree
Inheritance is a mechanism where certain CSS properties, when applied to a parent element, are automatically passed down to its child elements. This is separate from the cascade but works alongside it. Inheritance prevents you from having to set the same property on every single element -- you can set font-family on the <body> and all text elements inside will inherit that font.
Inherited vs Non-Inherited Properties
Not all CSS properties inherit by default. The general rule is: properties related to text and typography tend to inherit, while properties related to layout and the box model tend not to inherit. Here are the key properties in each category:
Properties that inherit by default:
colorfont-family,font-size,font-weight,font-styleline-height,letter-spacing,word-spacingtext-align,text-indent,text-transformvisibilitycursorlist-style,list-style-type,list-style-positiondirection
Properties that do NOT inherit by default:
margin,padding,borderwidth,height,min-width,max-widthdisplaybackgroundand all background propertiesposition,top,right,bottom,leftfloat,clearoverflowz-indexbox-shadow,opacity
border or padding from its parent, your page would look chaotic. That is why box model properties do not inherit. But if you had to set color and font-family on every single paragraph, list item, and span, writing CSS would be incredibly tedious. That is why typography properties do inherit.Controlling Inheritance with Special Keywords
CSS provides several special keywords that let you explicitly control how inheritance behaves for any property. These keywords are valid values for every CSS property.
The inherit Keyword
The inherit keyword forces a property to take its value from the parent element, even if the property does not normally inherit. This is useful when you want a child element to match its parent's styling for a non-inherited property.
Example: Forcing Inheritance with inherit
.parent {
border: 2px solid #333;
padding: 20px;
color: darkblue;
}
/* Border does not inherit by default,
but we can force it */
.child {
border: inherit; /* Now matches parent's border */
padding: inherit; /* Now matches parent's padding */
}
/* A practical use: making links inherit the text color */
a {
color: inherit; /* Link color matches surrounding text */
text-decoration: underline;
}The initial Keyword
The initial keyword resets a property to its default value as defined in the CSS specification -- not the browser's default style for that element, but the absolute initial value defined by the spec. For example, the initial value of display is inline (even for elements like <div> that browsers style as block).
Example: Using initial to Reset Properties
/* The browser gives h1 a large bold font-size,
but the spec's initial value for font-size is "medium" */
h1 {
font-size: initial; /* Resets to spec default, NOT the browser's h1 size */
}
/* The browser gives div display: block,
but the spec's initial value for display is "inline" */
div.inline-box {
display: initial; /* Becomes inline, not block! */
}initial. It resets to the CSS specification's initial value, which is not the same as the browser's default style for that element. If you want to reset an element to how the browser would normally style it, use revert instead.The unset Keyword
The unset keyword acts as either inherit or initial depending on the property. If the property naturally inherits (like color), unset behaves like inherit. If the property does not naturally inherit (like border), unset behaves like initial.
Example: The unset Keyword in Action
.parent {
color: navy;
border: 3px solid red;
}
.child {
color: unset; /* color inherits, so this = inherit = navy */
border: unset; /* border does not inherit, so this = initial = none */
}
/* A useful pattern: reset all styles on an element */
.reset-element {
all: unset; /* Resets every property based on whether it inherits or not */
}The revert Keyword
The revert keyword rolls a property back to the value it would have from the previous origin in the cascade. If used in an author stylesheet, it reverts to whatever the user stylesheet or user-agent stylesheet would apply. This is the keyword to use when you want an element to look like the browser's default styling.
Example: Reverting to Browser Defaults
/* Suppose your CSS removes list styling */
ul {
list-style: none;
padding: 0;
margin: 0;
}
/* But for one specific list, you want browser defaults back */
ul.browser-default {
list-style: revert; /* Gets back bullets */
padding: revert; /* Gets back browser padding */
margin: revert; /* Gets back browser margin */
}
/* revert is great for resetting specific components
without knowing exact browser default values */
.widget h2 {
font-size: revert; /* Browser's default h2 size */
margin: revert; /* Browser's default h2 margin */
}The all Property Shorthand
The all property is a shorthand that resets every CSS property on an element (except direction and unicode-bidi). It accepts the keywords inherit, initial, unset, and revert. This is extremely useful for creating isolated components or resetting third-party widget styles.
Example: Using the all Property
/* Reset absolutely everything to browser defaults */
.isolated-component {
all: revert;
}
/* Remove all inherited and default styles */
.clean-slate {
all: unset;
}
/* Force all properties to inherit from parent */
.mirror-parent {
all: inherit;
}Practical Specificity Debugging
Specificity issues are among the most common CSS bugs. Here is a systematic approach to diagnosing and fixing them:
- Open DevTools -- Right-click the element and choose "Inspect." The Styles panel shows all rules that target the element, ordered by specificity. Rules that are overridden have a strikethrough line.
- Read the struck-through styles -- These are the declarations that lost the specificity battle. DevTools shows you exactly which rule is winning.
- Calculate specificity -- Count the IDs, classes, and elements in both the winning and losing selectors. The winner should have a higher count.
- Fix with minimal changes -- Instead of adding
!important, try to restructure your selectors. Add a class to increase specificity or remove an over-specific parent selector that is causing problems.
.card__title--highlighted is specific by naming convention, not by selector nesting. Consider adopting a naming methodology for larger projects to prevent specificity issues from the start.Specificity in the Real World: A Worked Example
Let us walk through a realistic scenario. Imagine you are building a navigation component. You write a clean, class-based rule for your navigation links. But later, a teammate adds a more specific rule inside a parent ID selector. Your styles suddenly stop working, and you cannot figure out why. Here is what the code might look like:
Example: A Real-World Specificity Conflict
/* Your clean component style -- specificity 0,0,1,0 */
.nav-link {
color: #333;
text-decoration: none;
padding: 8px 16px;
}
/* A teammate's rule in a different file -- specificity 0,1,1,1 */
#site-header nav .nav-link {
color: blue;
text-decoration: underline;
}
/* Your style LOSES because 0,1,1,1 > 0,0,1,0 */
/* To fix without !important, you have several options: */
/* Option A: Match the specificity by adding context */
#site-header .nav-link {
color: #333;
text-decoration: none;
}
/* Option B (better): Refactor the teammate's rule to
use only classes, then both rules are at the same level */
/* Option C (best): Agree on a team convention like BEM
where all selectors use single classes */This scenario illustrates why teams benefit from CSS methodologies and code review. Specificity conflicts are rarely about who is "right" -- they are about establishing consistent patterns that the whole team follows. When everyone writes flat, class-based selectors, specificity conflicts essentially disappear.
Exercise 1: Specificity Calculation Challenge
Calculate the specificity of each of the following selectors and rank them from lowest to highest specificity. Write down the four-part specificity value (a,b,c,d) for each one:
div p span.container .content p#main .content pnav ul li a:hover#sidebar .widget h3.titlebody #content div.article p.intro::first-line
After calculating, create an HTML page with a single paragraph that has an ID, two classes, and is nested inside several elements. Write six different CSS rules targeting that paragraph with different colors. Verify in your browser that the rule with the highest specificity wins.
Exercise 2: Inheritance and Keyword Practice
Create an HTML page with a nested structure: a <div> parent containing a <section>, which contains a <p> with a <span> inside. Apply color, border, font-family, padding, and background-color to the outer <div>. Then observe which properties are inherited by the nested elements. Next, use the inherit keyword on the <section> to force border and padding to inherit. Use initial on the <p> to reset color and font-family. Finally, use unset on the <span> for all five properties and observe whether each property inherits or resets to its initial value. Document your findings by adding comments to your CSS explaining the behavior of each keyword.