How Specificity Works
Each CSS selector has a specificity score — a three-column value written as (a, b, c). The browser compares scores column by column, left to right. The rule with the highest score wins. If scores are equal, the rule declared last in the source wins (the cascade).
| Category | What counts | Score contribution |
|---|---|---|
| Column A — ID selectors | #id |
(1, 0, 0) |
| Column B — Class, attribute, pseudo-class | .class, [attr], :hover, :nth-child() |
(0, 1, 0) |
| Column C — Element, pseudo-element | div, p, a, ::before |
(0, 0, 1) |
| Inline style | style="..." attribute |
(1, 0, 0, 0) |
| Universal / combinators | *, >, +, ~, |
(0, 0, 0) |
/* Selector Score (a, b, c) */
p /* (0, 0, 1) — 1 element */
.intro /* (0, 1, 0) — 1 class */
#header /* (1, 0, 0) — 1 ID */
div p /* (0, 0, 2) — 2 elements */
.nav li /* (0, 1, 1) — 1 class, 1 element */
.nav li a /* (0, 1, 2) — 1 class, 2 elements */
#header .nav li a /* (1, 1, 2) — 1 ID, 1 class, 2 elements */
.card:hover /* (0, 2, 0) — 1 class, 1 pseudo-class */
input[type="text"] /* (0, 1, 1) — 1 attribute, 1 element */
ul li:first-child /* (0, 1, 2) — 1 pseudo-class, 2 elements */
#nav > ul > li > a:hover /* (1, 1, 3) — 1 ID, 1 pseudo-class, 3 elements */
Comparing Specificity
/* Which color wins? */
p { color: blue; } /* (0,0,1) */
.text { color: red; } /* (0,1,0) */
/* Winner: .text → red (0,1,0 beats 0,0,1) */
#main p { color: green; } /* (1,0,1) */
.section .text { color: orange; } /* (0,2,0) */
/* Winner: #main p → green (1 in column A wins regardless of B and C) */
/* Same specificity — last one in source wins */
.btn { background: blue; } /* (0,1,0) */
.cta { background: red; } /* (0,1,0) */
/* <button class="btn cta"> → red wins (declared last) */
!important
!important overrides all specificity — including inline styles. It adds a separate "important" layer above the normal specificity scale. When two !important declarations compete, the one with higher specificity wins.
/* !important beats everything in the normal cascade */
p { color: blue !important; } /* wins over: */
#main p { color: red; } /* even though (1,0,1) > (0,0,1) */
/* Two !important — specificity wins */
p { color: blue !important; } /* (0,0,1) with !important */
.text { color: red !important; } /* (0,1,0) with !important — wins */
/* Utility class pattern — legitimate use of !important */
.text-center { text-align: center !important; }
.hidden { display: none !important; }
.mt-0 { margin-top: 0 !important; }
The Cascade Order
When specificity is equal, the cascade applies — rules are resolved in this order (later stages override earlier ones):
1. Browser default stylesheet (user-agent styles)
2. User stylesheet (accessibility overrides in browser settings)
3. Author stylesheet (your CSS) — by order:
a. External stylesheets (in link order)
b. Internal <style> blocks (in document order)
c. Inline styles (style="" attribute)
4. !important in author styles
5. !important in user styles (accessibility overrides — highest priority)
Inheritance
Some properties inherit down to children automatically (color, font-*, line-height). Others don't (border, padding, margin, background). You can force or reset inheritance with the keywords below.
/* inherit — force the element to inherit its parent's value */
.child { color: inherit; }
/* initial — reset to the browser's initial value for that property */
button { background: initial; } /* removes browser button background */
/* unset — inherit if naturally inherited, otherwise initial */
.reset { all: unset; } /* strips all styles from an element */
/* revert — roll back to browser's user-agent stylesheet value */
h1 { font-size: revert; }
Avoiding Specificity Wars
Specificity wars happen when you pile on selectors trying to override each other. The solution is to keep specificity low and consistent.
/* BAD — high ID specificity locks you in */
#page #main-content .sidebar .widget h3 { color: red; }
/* GOOD — single class, low specificity */
.widget-title { color: red; }
/* BAD — escaping with !important */
.btn { color: blue !important; }
/* GOOD — raise specificity slightly instead */
.btn.btn-primary { color: #1572B6; }
/* Use :where() to write complex selectors without adding specificity */
:where(#app) .btn { color: #1572B6; } /* (0,1,0) — not (1,1,0) */
/* Layers: @layer lets you control cascade order independent of specificity */
@layer base, components, utilities;
@layer base {
a { color: blue; }
}
@layer components {
.link { color: #1572B6; } /* wins over base — even same specificity */
}
@layer utilities {
.text-red { color: red; } /* wins over components */
}
How Specificity Is Actually Calculated
When two rules target the same element, the browser scores each selector as three numbers — (IDs, classes, elements) — and the higher score wins.
| Selector | IDs | Classes | Elements | Score |
|---|---|---|---|---|
p | 0 | 0 | 1 | 0,0,1 |
.btn | 0 | 1 | 0 | 0,1,0 |
nav .btn | 0 | 1 | 1 | 0,1,1 |
#hero | 1 | 0 | 0 | 1,0,0 |
Compare left-to-right: one ID beats any number of classes; one class beats any number of elements. If scores tie, the rule that appears later in the CSS wins (source order).
!important is a code smell, not a fix
It overrides the whole cascade, so the next override needs another !important, and soon nothing is predictable. Fix specificity properly instead — usually by using a single, well-named class rather than deep #id div ul li a chains. Keep specificity flat and low.
🏋️ Practical Exercise
- Calculate the specificity of three different selectors.
- Override a class rule with an id rule.
- See an inline style beat a stylesheet rule.
- Use
!importantto override everything. - Inspect the winning rule in DevTools.
🔥 Challenge Exercise
Given a paragraph targeted by an element selector, a class, an id, and an inline style, predict the final color using specificity rules, then verify in DevTools. Explain the specificity weights (inline, id, class, element) and why !important should be a last resort.
📋 Summary
- Specificity score is (a, b, c): a = IDs, b = classes/attributes/pseudo-classes, c = elements/pseudo-elements.
- Compare column by column left to right. Column A wins over any number of B or C.
- Equal specificity → last rule in source wins (the cascade).
- Inline styles beat all selector specificity (unless
!importantis involved). !importantis a nuclear option — overrides everything in the normal layer. Legitimate for utility classes (.hidden,.sr-only).:where()is zero-specificity — write complex selectors without raising the score.@layercreates cascade layers — later layers win regardless of specificity within the layer.- Keep selectors flat (1 class level) to avoid specificity wars.
Interview Questions
- How is CSS specificity calculated?
- What is the specificity order from highest to lowest?
- How does
!importantaffect specificity? - What wins when two selectors have equal specificity?
- Why is high specificity hard to maintain?
Related Topics
FAQ
No — only the selector type and count affect the specificity score. Source order only matters as a tiebreaker when two rules have the same specificity score. A class selector (.btn) always loses to an ID selector (#btn) regardless of where they appear in the stylesheet.
Technically infinite — specificity scores do not overflow between columns. Even .a.b.c.d.e.f.g.h.i.j.k (11 classes = score 0,11,0) does not beat #id (score 1,0,0), because column A is compared first and 1 beats 0. This is why using IDs in selectors creates a specificity problem that can only be resolved with another ID or !important.
@layer (CSS Cascade Layers, introduced in 2022) lets you group CSS rules into named layers with a defined order. Rules in a higher-priority layer win over all rules in lower-priority layers, regardless of specificity. This is especially useful when integrating third-party CSS (reset, component libraries) — put them in a low-priority layer so your application styles always win without needing !important. Browser support is above 93% as of 2026.

