Mid-level 6 min · March 05, 2026

CSS Specificity — .btn.btn-primary Beats Your Override

Bootstrap's chained selector (0,0,2,0) overrides your custom class even when loaded first.

N
Naren · Founder
Plain-English first. Then code. Then the interview question.
About
 ● Production Incident 🔎 Debug Guide
Quick Answer
  • CSS cascade determines which rule applies by filtering through origin, importance, specificity, then source order
  • Specificity is a four-column score: inline > ID > class/attribute/pseudo-class > element/pseudo-element
  • Inherited values have zero specificity — a direct child rule always beats an inherited parent value
  • @layer lets you reorder priority without touching selector specificity — modern alternative to !important
  • Source order is the final tiebreaker: later rules override earlier ones when everything else is equal
  • !important escalates a rule to an importance layer — user !important beats author !important
Plain-English First

Imagine four judges scoring a talent contest. The head judge's score always beats everyone else's. If two judges give the same score, the one who voted last wins. CSS works exactly the same way — every style rule gets a 'score' based on how specific it is, and the highest score wins. That's why slapping 'color: red' on a paragraph sometimes does absolutely nothing.

Every developer has been there — you write a perfectly valid CSS rule, refresh the browser, and nothing changes. You try adding it inline. Still nothing. You Google furiously, someone tells you to use !important, and suddenly it works, but now you've just buried a landmine in your codebase. The real problem isn't your syntax. It's that you haven't yet made friends with the two most powerful forces in CSS: the cascade and specificity.

CSS was designed to let multiple stylesheets coexist — your own code, a UI framework like Bootstrap, browser defaults, and user preferences all need to play nicely together. Without a clear, deterministic system for resolving conflicts, every page would be chaos. Specificity and the cascade are that system. They define a strict hierarchy so the browser always knows exactly which rule to apply, every single time, with zero ambiguity.

By the end of this article you'll be able to read any selector and instantly know its weight, predict which rule wins in a conflict without opening DevTools, and — most importantly — architect your stylesheets so you never need to reach for !important again. You'll also understand why that one Bootstrap style keeps overriding your custom theme, and exactly how to fix it cleanly.

How the Cascade Decides Which Rule Wins First

The 'cascade' in CSS literally means a waterfall — styles flow down through layers, with each layer able to override the one before it. Before specificity even enters the picture, the cascade filters rules through three stages in this exact order: origin and importance, then specificity, then source order.

Origin refers to WHERE the style came from. Browser default stylesheets (called user-agent styles) sit at the bottom. User stylesheets (accessibility overrides set by the visitor) sit above that. Your author stylesheets — the CSS you write — sit above that. So your styles already beat browser defaults just by existing, which is why you can override the default blue on anchor tags without any fight.

Importance is the nuclear option. Adding !important to a declaration pushes it to a separate, higher-priority layer within its origin. Author !important rules beat normal author rules, and user !important rules beat author !important rules (intentionally, to protect accessibility). This is why you should almost never write !important yourself — you're handing a loaded gun to a system you don't fully control.

Only when origin and importance are tied does the browser look at specificity. Source order is the final tiebreaker — if two rules have identical origin, importance, and specificity, whichever one appears later in the CSS wins. That last-write-wins rule is why import order in your bundler actually matters.

cascade-layers.cssCSS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/* ─────────────────────────────────────────────────
   Demonstrating cascade origin + source order.
   All three rules target the same <h1 class="page-title">.
   Watch how the browser resolves the conflict.
───────────────────────────────────────────────── */

/* Rule 1: Appears first in the file — will lose to Rule 2 */
h1 {
  color: steelblue;        /* plain element selector */
  font-size: 2rem;
}

/* Rule 2: Same selector, appears LATER — source order wins over Rule 1 */
h1 {
  color: tomato;           /* overwrites steelblue because it comes after */
}

/* Rule 3: Higher specificity (.page-title) — beats both rules above
   regardless of source position */
.page-title {
  color: rebeccapurple;    /* wins because class > element selector */
}

/* Rule 4: !important — overrides everything above, even Rule 3.
   The h1 will render in gold. But notice: we still have
   the cascade stages to thank for even getting here. */
h1 {
  color: gold !important;  /* nuclear option — jumps to importance layer */
}
Watch Out: !important Doesn't Override Everything
A user's !important rule (set in their browser for accessibility) beats your author !important rule. Never use !important to fight browser accessibility overrides — you'll lose, and you'll harm your users.
Production Insight
Third-party scripts often inject inline styles with !important for overlays or cookie banners. Your CSS can't override those — you must remove the inline style via JavaScript.
If you see a rule with !important that you didn't write, check the user-agent origin in DevTools — it might be a browser extension.
Layer order (via @layer) now provides a surgical override path that doesn't involve !important.
Key Takeaway
Cascade filters by origin first — browser defaults lose to author styles.
!important escalates a rule within its origin but user !important wins over author !important.
Source order only matters when origin, importance, and specificity are identical.

Specificity Scores Explained — The 0-0-0-0 Point System

Specificity is calculated as a four-column score: [Inline, IDs, Classes/Attributes/Pseudoclasses, Elements/Pseudoelements]. Think of it like a version number — 1.0.0.0 will always beat 0.99.99.99, because columns are never carried over. A thousand class selectors can never outrank even one ID selector.

Here's the weight of each selector type: — Inline styles (style="..." on the HTML element): score 1,0,0,0 — ID selectors (#main-nav): score 0,1,0,0 — Class selectors (.card), attribute selectors ([type='text']), pseudo-classes (:hover, :nth-child): score 0,0,1,0 — Element selectors (div, p, h1), pseudo-elements (::before, ::after): score 0,0,0,1 — The universal selector (*) and combinators (+, >, ~, ' '): score 0,0,0,0

The :not(), :is(), and :has() pseudo-classes are 'transparent' — their own contribution is zero, but the specificity of their arguments counts. So :not(#sidebar) contributes 0,1,0,0 because #sidebar is an ID.

The practical implication is huge: if you're building a component library, leaning heavily on ID selectors traps every consumer of that library in a specificity war they'll need !important to escape. Use classes exclusively and you gift them a flat, overridable surface.

specificity-calculator.cssCSS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/* ─────────────────────────────────────────────────
   Specificity score is shown as [Inline, ID, Class, Element]
   for each selector. The highest score wins.
   HTML snippet being targeted:

   <nav id="main-nav" class="site-nav">
     <a href="/" class="nav-link active">Home</a>
   </nav>
───────────────────────────────────────────────── */

/* Score: [0, 0, 0, 1] — 1 element selector */
a {
  color: gray;
}

/* Score: [0, 0, 1, 0] — 1 class selector (beats the 'a' rule) */
.nav-link {
  color: steelblue;
}

/* Score: [0, 0, 2, 0] — 2 class selectors (.site-nav + .nav-link) */
.site-nav .nav-link {
  color: darkcyan;
}

/* Score: [0, 0, 2, 1] — 2 classes + 1 element (beats line above) */
.site-nav .nav-link a {
  color: dodgerblue;
}

/* Score: [0, 1, 0, 0] — 1 ID selector (beats ALL class combos above) */
#main-nav a {
  color: navy;             /* wins — ID outranks any number of classes */
}

/* Score: [0, 1, 1, 0] — 1 ID + 1 class (beats plain #main-nav a) */
#main-nav .active {
  color: crimson;          /* this is the final winner for .active link */
}

/* Score: [1, 0, 0, 0] — inline style beats everything above */
/* HTML: <a style="color: limegreen;"> would override even #main-nav .active */
Pro Tip: Visualise Specificity as a 4-Digit Lock Code
Write your selector's score as four digits side by side: 0110 beats 0030 beats 0004. You can't borrow from adjacent columns — 0,0,10,0 will NEVER beat 0,1,0,0. This mental model stops you from ever being surprised again.
Production Insight
Using IDs in a component library or design system is a productivity trap — consumers will need to fight your specificity to customize components.
The :is() pseudo-class can cause unexpected specificity boosts because its highest argument's specificity counts. Example: :is(#sidebar, .card) .btn has specificity 0,1,1,0 even though only the ID argument triggered.
When debugging specificity in DevTools, the Styles panel shows the selector and its specificity breakdown — use that to understand why your rule lost.
Key Takeaway
Specificity score is one of four columns that never carry over.
ID (0,1,0,0) beats infinite classes (0,0,n,0).
:not() and :is() are transparent but their argument specificity counts.

Real-World Specificity Architecture — Keeping Your CSS Sane at Scale

Understanding specificity is one thing. Designing a stylesheet that doesn't collapse under its own weight six months later is another. The most battle-tested approach is the Specificity Graph — your rules should flow from low specificity (global resets, element defaults) at the top of your CSS to higher specificity (utility overrides, state modifiers) at the bottom. If you see specificity jumping up and down like a heartbeat monitor, that's a sign you're fighting the cascade instead of working with it.

Methodologies like BEM (Block__Element--Modifier) solve this by mandating that every selector is a single flat class. No nesting, no IDs, no element qualifiers. The entire stylesheet hovers around a specificity of 0,0,1,0. When everything is equally specific, source order takes over, and source order is predictable and readable.

CSS Cascade Layers (@layer, available in all modern browsers since 2022) take this further by letting you explicitly define an ordering of layers. Rules in a later layer win over earlier layers regardless of selector specificity. This is a game-changer for integrating third-party CSS — you can dump a framework into a low-priority layer and override it with your own low-specificity classes without a single !important.

cascade-layers-architecture.cssCSS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
/* ─────────────────────────────────────────────────
   Using @layer to control specificity without
   selector gymnastics or !important abuse.

   Layer priority (last listed = highest priority):
   reset → framework → components → utilities
───────────────────────────────────────────────── */

/* Declare layer order FIRSTthis line controls everything below */
@layer reset, framework, components, utilities;

/* Anything in 'reset' has the LOWEST priority */
@layer reset {
  * {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
  }
}

/* Bootstrap (or any third-party CSS) gets dumped into 'framework'.
   Even its high-specificity ID rules won't escape this layer. */
@layer framework {
  /* Simulating a hypothetical Bootstrap rule with high specificity */
  #app .btn.btn-primary {
    background-color: #0d6efd;   /* Bootstrap blue */
    color: white;
    border-radius: 4px;
  }
}

/* Our component styles — single flat classes. Specificity: [0,0,1,0] */
@layer components {
  .btn-primary {
    /* This LOW-specificity rule BEATS the high-specificity Bootstrap rule
       above because 'components' layer is declared AFTER 'framework'. */
    background-color: #7c3aed;   /* our brand purple */
    color: white;
    border-radius: 8px;          /* our brand border radius */
    padding: 0.5rem 1.25rem;
  }
}

/* Utility classes are last — they always win (like Tailwind's approach) */
@layer utilities {
  .bg-transparent {
    background-color: transparent !important;
    /* !important inside a layer only escalates within that layer,
       not across all layers — much safer than global !important */
  }
}
Interview Gold: @layer Is the Answer to 'How Do You Handle Third-Party CSS?'
@layer lets you wrap any imported CSS in a named layer with lower priority than your own code. Mention this in interviews when asked about CSS architecture — most candidates only know !important and selector specificity battles.
Production Insight
When migrating a legacy codebase to @layer, be aware that unlayered styles always win over layered styles, regardless of their own specificity. If you have existing CSS outside any layer, it will override everything inside your layers — a common migration trap.
The @layer statement must appear before any layered rules. If you declare layered rules before the @layer order statement, your layers are in the order they first appeared, which may not be what you intended.
In production, use a polyfill for older browsers if you need to support IE, but since 2022 all evergreen browsers support @layer.
Key Takeaway
@layer lets you reorder priority — later layers win over earlier layers, overriding selector specificity.
Wrap third-party CSS in a low-priority layer for clean overrides without !important.
Unlayered styles always beat layered styles — migration requires careful planning.

Inheritance — The Hidden Cascade That Runs Underneath

Specificity and cascade govern which declared rule wins. But there's a parallel system running quietly beneath it: inheritance. Some CSS properties automatically travel down the DOM tree from parent to child without you writing a single extra rule. Color, font-family, font-size, line-height, and text-align are the most common inherited properties. Layout properties like margin, padding, border, width, and display are intentionally non-inherited because inheriting them would break virtually every layout.

You can manually control inheritance with four special keyword values: inherit forces a property to take its parent's computed value, even for non-inherited properties. initial resets a property to its CSS specification default (which might surprise you — the initial value of display is inline for all elements). unset acts like inherit for naturally inherited properties and initial for non-inherited ones. revert resets to the browser's default stylesheet value, which is much more useful than initial in practice.

The interplay between inheritance and specificity trips people up because an inherited value has a specificity of zero — it will always lose to even the weakest explicit declaration on the element itself. So if you set color: red on a child element with just an element selector (specificity 0,0,0,1), it beats a color: blue set on the parent even with an ID selector (0,1,0,0), because the child's rule is a direct declaration, not an inherited value.

inheritance-and-keywords.cssCSS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/* ─────────────────────────────────────────────────
   HTML structure:
   <section id="article-body" class="content-section">
     <p class="lead-paragraph">Intro text <span>highlighted</span></p>
     <p>Regular paragraph</p>
   </section>
───────────────────────────────────────────────── */

/* Set font properties on the container — these WILL inherit downward */
#article-body {
  font-family: 'Georgia', serif;   /* inherits to all descendants */
  font-size: 1.125rem;
  color: #2d2d2d;                  /* inherits to all descendants */
  line-height: 1.7;
  border: 2px solid #ccc;          /* does NOT inherit — layout prop */
  padding: 1.5rem;                 /* does NOT inherit */
}

/* The span inside .lead-paragraph inherits color from #article-body.
   Even though #article-body has a high-specificity ID selector,
   its color is only INHERITED here — not a direct declaration.
   So this low-specificity rule wins. */
.lead-paragraph span {
  color: #7c3aed;    /* direct declaration [0,0,1,1] beats inherited value */
  font-weight: bold;
}

/* Demonstrating 'inherit', 'initial', 'unset', 'revert' */
.content-section p {
  /* Force border to inherit — unusual, but shows the keyword works */
  border: inherit;       /* paragraphs now get the section's 2px border */

  /* Reset color to the browser's default (black) — more useful than initial */
  /* color: revert; */   /* uncomment to see browser default color apply */

  /* Reset completely to CSS spec default'transparent' for color */
  /* color: initial; */  /* uncomment to see text become transparent! */

  margin-bottom: 1rem;   /* NOT inherited naturally, so we declare explicitly */
}
Watch Out: 'initial' Is Not 'Browser Default'
color: initial sets color to the CSS spec's initial value (black), but font-size: initial sets it to 'medium' (roughly 16px) and display: initial makes ANY element inline. Use revert when you want 'back to how the browser normally styles this' — it's almost always what you actually mean.
Production Insight
A common bug: setting color on a parent with an ID selector, then expecting it to apply to a child that has a plain a element selector. The child's a rule overrides the inherited color, even though the parent's rule has higher specificity. DevTools shows the child's computed color from the a rule — not the parent's.
When a property is not inherited (like border), you must explicitly set it on each child or use inherit keyword. This is why CSS reset stylesheets often set box-sizing: border-box on everything using * { box-sizing: border-box; } — because it's not inherited.
The all shorthand property accepts inherit, initial, unset, revert. Use all: unset to completely reset an element's styles — but be careful, it resets everything including display.
Key Takeaway
Inherited values have zero specificity — a direct child declaration always beats inherited parent values.
Use revert to reset to browser defaults, not initial.
Only certain properties inherit by default; for others, use inherit keyword.

Advanced Debugging: DevTools Specificity Inspector & the Cascade Override Table

Modern browser DevTools (Chrome, Firefox, Edge) provide powerful tools to debug specificity battles. In Chrome, the Styles panel shows every rule matched to the element, ordered by specificity (highest at top). Each rule's selector is shown with its specificity inline. Hovering over a rule reveals its origin (user agent, author, user), layer (if any), and source file.

The Computed panel shows the final value of each property and the source declaration that set it. Clicking the arrow next to a property shows a cascade override table — a list of all declarations that tried to set that property, ranked by cascade priority. This is the fastest way to see exactly why your rule lost.

Pro tip: When debugging, right-click the element and select 'Inspect'. In the Styles panel, look for the 'Cascade layers' section that lists all layers defined in the page. You can toggle layers on and off to see how your styles behave without that layer. Also, the 'Inherited from' section shows inherited values — if you see a property there but not in the direct styles, that inheritance is happening.

debugging-example.cssCSS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/* Example: A real override conflict you can reproduce */

/* File: main.css (loaded first) */
@layer framework, components;

@layer framework {
  /* Simulate Bootstrap button */
  .btn {
    padding: 0.5rem 1rem;
    border-radius: 4px;
    background: #0d6efd;
    color: white;
    border: none;
  }
}

@layer components {
  /* Our attempt to override */
  .btn-primary {
    background: #7c3aed;
    border-radius: 8px;
    padding: 0.75rem 1.5rem;
  }
}

/* Inspect in DevTools:
   - See that .btn-primary properties are not applied because layer 'components' is not after 'framework'.
   - Actually they ARE applied because components is after framework.
   - But compute shows background: #0d6efd? No, it shows #7c3aed because layer priority beats specificity.
   - This demonstrates @layer works.
*/
DevTools Shortcut: Toggle Layers in the Styles Panel
Click the 'Cascade Layers' badge in the Styles panel to see all layers. You can disable a layer to see how the page looks without it — great for isolating third-party CSS conflicts.
Production Insight
When a bug report says 'the button is blue instead of purple', don't start adding !important. Open DevTools, check the Layers badge, and see if your CSS is in a lower-priority layer than the framework.
The cascade override table (click the small arrow next to any property in Computed panel) shows every declaration that tried to apply to that property, ordered by cascade priority. This reveals inheritance chains and layer conflicts.
If you're using CSS custom properties (variables), the computed value shows the resolved variable — click the arrow to see the variable definition and any fallback chain.
Key Takeaway
DevTools Cascade Layers badge shows layer priority at a glance.
The cascade override table (Computed panel) reveals exactly why a rule lost — specificity, origin, layer, or source order.
Never debug CSS without DevTools — it shows the full cascade resolution path.
● Production incidentPOST-MORTEMseverity: high

The Bootstrap Theme That Wouldn't Override — A Specificity Ambush

Symptom
Custom .btn-primary class with background-color: #7c3aed shows Bootstrap's #0d6efd in DevTools. The computed style stays blue despite the custom rule appearing later in the file and having equal specificity.
Assumption
The developer assumed that adding a more specific selector (e.g., .container .btn-primary) would override Bootstrap's .btn.btn-primary rule. They added classes, then IDs, then resorted to !important, which created a sprawling chain of overrides across the codebase.
Root cause
Bootstrap's selector .btn.btn-primary (specificity 0,0,2,0) was being loaded after the custom CSS in the HTML <link> order, so source order was not the issue. The real problem was that Bootstrap's CSS was not wrapped in a lower-priority layer, so its rules competed at the same origin layer as the custom CSS. Without @layer, the browser respects selector specificity and source order, but Bootstrap's rule had slightly higher specificity due to chaining two classes.
Fix
Imported Bootstrap CSS inside a @layer declaration: @layer framework { @import 'bootstrap.css'; }. Then placed custom styles in a later layer (or default layer). The custom .btn-primary rule (specificity 0,0,1,0) now overrides Bootstrap's rule (0,0,2,0) because layer order beats specificity. Zero !important needed.
Key lesson
  • Never fight third-party CSS with specificity escalation — use @layer to reorder priorities instead.
  • !important on dozens of rules is a sign of broken architecture, not a solution.
  • Always load third-party stylesheets in a low-priority @layer so your own low-specificity rules can override them cleanly.
  • When debugging override failures, check the cascade layer order first — it beats everything else.
Production debug guideWhen your style doesn't apply, use this systematic approach instead of randomly adding !important.5 entries
Symptom · 01
Computed style shows unexpected value in DevTools; your rule is crossed out
Fix
Inspect the element, go to 'Styles' panel. Hover over the crossed-out rule to see why it's overridden (origin, specificity, source order, or layer). The tooltip tells you exactly which rule beat yours.
Symptom · 02
Your rule never appears in DevTools at all
Fix
Check for invalid selector syntax (missing . or #, typos). Verify the stylesheet is actually loaded (Network tab). If using CSS Modules or styled-components, ensure the generated class name matches the rendered DOM.
Symptom · 03
!important works but feels wrong; codebase has many !important rules
Fix
Stop. Open DevTools, locate the winning rule. Determine if it's a specificity war or a layer priority issue. Use @layer to restructure rather than adding more !important.
Symptom · 04
A parent style (color, font) is not applying to child element
Fix
Check if the child has a direct declaration for that property. Even a low-specificity element selector on the child beats any inherited value. Remove the child's direct rule or use 'inherit' keyword.
Symptom · 05
Inline style is not being overridden
Fix
Inline styles have specificity 1,0,0,0. The only way to beat them is with !important on a rule (but user !important can beat author !important). Better: remove the inline style and use a class.
★ Quick Fix: Why Your CSS Isn't ApplyingThree commands to diagnose specificity and cascade issues in seconds.
Rule ignored without clear reason
Immediate action
Open DevTools Elements panel, inspect target element, read 'Styles' tab
Commands
Computed tab: see final value and last winning declaration
Check 'User Agent Stylesheet' origin in Styles panel
Fix now
Add a higher-specificity selector or wrap third-party CSS in @layer
A CSS variable or custom property not applying+
Immediate action
Check if the variable is defined on a parent that is not inherited
Commands
Verify var(--name) doesn't have a typo
Check for fallback: var(--name, fallback) being used
Fix now
Define the variable on the element itself or use inherit
Bootstrap/Tailwind styles overriding custom code+
Immediate action
Check @layer declaration order
Commands
In DevTools, check if the framework rule is inside a layer named 'framework' or similar
Look for the layer badge next to the rule in Styles panel
Fix now
Wrap framework import in @layer framework { } and ensure your code is in a later layer (or no layer)
Cascade Resolution Table
ConceptWhat It DoesControlled ByOverride MethodSpecificity Score
Inline StyleApplies directly on HTML elementstyle attribute in HTML!important in stylesheet1,0,0,0
ID Selector (#id)Targets unique element by IDCSS ruleAnother ID + higher specificity or @layer0,1,0,0
Class Selector (.class)Targets elements by class nameCSS ruleHigher specificity rule or later @layer0,0,1,0
Attribute Selector ([attr])Targets elements by attributeCSS ruleSame as class — equal weight0,0,1,0
Pseudo-class (:hover)Targets element stateCSS ruleSame as class — equal weight0,0,1,0
Element Selector (div)Targets by tag nameCSS ruleAny class/ID/inline above it0,0,0,1
Universal Selector (*)Targets everythingCSS ruleLiterally anything else0,0,0,0
Inherited ValueFlows from parent automaticallyParent's declarationAny direct declaration on child0 (no specificity)
!importantForces rule into importance layerCSS rule keywordUser !important or later @layer !importantBypasses normal scoring
@layerGroups rules into named priority tiersCSS @layer directiveDeclaring your layer later in the orderLayer order beats specificity

Key takeaways

1
The cascade filters rules through three stages in strict order
origin/importance first, then specificity, then source order — specificity only matters when origin and importance are tied
2
Specificity scores are four-column values [Inline, ID, Class, Element] that never carry over between columns
100 classes (0,0,100,0) can never beat a single ID (0,1,0,0)
3
Inherited values carry zero specificity
a direct element selector (0,0,0,1) on a child always beats a colour inherited from a parent with a high-specificity ID selector
4
@layer is the modern, surgical solution to third-party CSS conflicts
it lets you declare a priority hierarchy where layer order beats selector specificity, eliminating the need for !important in most real-world override scenarios
5
Always debug CSS using DevTools' cascade layers badge and computed panel cascade override table
they reveal the exact reason a rule lost

Common mistakes to avoid

3 patterns
×

Piling on classes to 'boost' specificity

Symptom
Selectors like .nav .nav-list .nav-item .nav-link.active that still lose to a single existing ID rule
Fix
Flatten your selectors to BEM-style single classes (.nav__link--active) and keep your specificity graph flat. If something keeps winning, use @layer to restructure priority rather than stacking more classes.
×

Using !important as a first resort instead of a last resort

Symptom
Your codebase has !important on dozens of rules, overrides start requiring !!important (which doesn't exist), DevTools shows rules crossed out in a long chain
Fix
Trace the winning rule in DevTools, understand WHY it wins (origin, specificity, or order), then fix the architecture — use @layer to contain third-party CSS and keep your own selectors at consistent specificity.
×

Confusing 'initial' with 'browser default' when resetting styles

Symptom
Using color: initial expecting to restore the browser's black text, but on custom-styled elements it renders as transparent or an unexpected colour
Fix
Use color: revert instead, which resets to the browser's user-agent stylesheet value, not the abstract CSS spec default. revert is almost always the correct semantic choice when 'undoing' a style.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01SENIOR
If you have an element matched by both '#sidebar .card' and '.main-conte...
Q02SENIOR
What's the difference between !important and @layer for overriding third...
Q03SENIOR
An inherited color value from a parent with a high-specificity ID select...
Q01 of 03SENIOR

If you have an element matched by both '#sidebar .card' and '.main-content .card.featured', which rule wins and why? Walk me through the specificity calculation for each selector.

ANSWER
#sidebar .card: specificity 0,1,1,0 (one ID + one class). .main-content .card.featured: specificity 0,0,3,0 (three classes). The first selector wins because the ID column (1) beats any number of class columns (3). This demonstrates the column isolation rule: 0,1,0,0 always beats 0,0,n,0.
FAQ · 3 QUESTIONS

Frequently Asked Questions

01
Why is my CSS class not overriding a style from Bootstrap?
02
Does the order of CSS rules in a file actually matter?
03
What's the difference between 'inherit', 'initial', 'unset', and 'revert'?
🔥

That's HTML & CSS. Mark it forged?

6 min read · try the examples if you haven't

Previous
Semantic HTML Explained
9 / 16 · HTML & CSS
Next
CSS Preprocessors — SASS LESS