Skip to main content

Specificity and the Cascade

When several declarations set the same property on an element, the cascade picks one. It compares, in this order:

  1. Origin and importance: browser, user and author styles; !important reverses their order.
  2. The style attribute beats any selector.
  3. Cascade layers (@layer): later layers win; unlayered styles beat all layers (without !important).
  4. Specificity: (id, class, type), compared left to right.
  5. Source order: the last declaration wins.

A property that is not set at all is inherited from the parent (text properties such as color, font) or takes its initial value (margin, border).

Details​

Specificity​

Specificity is three numbers, (A, B, C), compared left to right: one id beats any number of classes.

Counts asExampleSpecificity
A: id#header(1, 0, 0)
B: class, attribute, pseudo-class.btn, [type='text'], :hover(0, 1, 0)
C: type, pseudo-elementp, ::before(0, 0, 1)
Nothing: *, combinators (>, +, ~, space)ul > li: only the two types count(0, 0, 2)
:is(), :not(), :has(): their most specific argument:is(#a, .b) p(1, 0, 1)
:where(): always zero:where(#a, .b) p(0, 0, 1)
Nesting &: like :is() around the parent selector#a, .b { & p {} } = :is(#a, .b) p(1, 0, 1)
:nth-child(… of S): the pseudo-class plus the most specific in S:nth-child(2 of .item)(0, 2, 0)
  • The style attribute is not specificity: it is a separate, earlier step of the cascade and beats any selector without !important.
  • :where() is for library and reset styles that any user selector should be able to override.
  • Because nesting works like :is(), & takes the specificity of the most specific selector in the parent list, even for elements matched by the less specific one.

Origin and !important​

From the weakest to the strongest:

  1. Browser (user-agent) styles.
  2. User styles (browser settings, a user stylesheet).
  3. Author styles: the site's CSS.
  4. CSS animations (@keyframes).
  5. Author !important.
  6. User !important.
  7. Browser !important.
  8. CSS transitions.

!important lifts a declaration into a separate, stronger group, and inside it the order of origins is reversed: user !important beats author !important, so the user's accessibility settings win.

Cascade Layers​

@layer reset, base, components, utilities;
  • Layers are ordered by first appearance; one @layer a, b, c; statement up front fixes the order. Later layers win, whatever the specificity inside them: .btn in utilities beats #id .btn in components.
  • Unlayered styles beat all layers. Third-party CSS can be put into a low layer with @import url(lib.css) layer(vendor);.
  • !important reverses the order of layers: an !important declaration in the earliest layer wins, and layered !important beats unlayered !important. This lets a reset protect a rule that must not be overridden.
  • Nested layers (@layer components.button) and anonymous layers are possible.

@scope​

@scope (.card) to (.card-content) { img { … } } applies the rules only inside .card but not inside .card-content (the limit itself is excluded too); when specificity is equal, the rule from the closer scope root wins (this step comes before source order). Browser support: Baseline 2026, newly available.

Inheritance and the Global Keywords​

  • Inherited properties: mostly text — color, font-*, line-height, text-align, visibility, cursor, and all custom properties. Not inherited: box and layout — margin, padding, border, width, background, display, position.
  • Form controls do not inherit font by default: the browser stylesheet sets it. A reset adds font: inherit to button, input, select, textarea.
KeywordValue
inheritthe parent's computed value
initialthe initial value from the spec (display: initial is inline)
unsetinherit for an inherited property, initial for the rest
revertrolls back to the previous origin: usually the browser's default style
revert-layerrolls back to the previous cascade layer (from unlayered styles, to the layered ones); with no layer left, works as revert
all: …sets every property except direction, unicode-bidi and custom ones

Example​

@layer base, components;

@layer components {
/* (1, 1, 0), but loses to .btn below: unlayered styles beat layers */
#app .btn {
color: red;
}
}

/* Unlayered: beats the layered rule despite lower specificity */
.btn {
color: green;
padding: 4px;
}

/* (0, 1, 1): beats .btn (0, 1, 0), so the padding is 8px */
button.btn {
padding: 8px;
}

/* (0, 0, 1): any class overrides it */
:where(.card, .panel) button {
font-weight: bold;
}

Follow-up Questions​

  • Why avoid !important? Only another !important can beat it (more specific, later in the source or in an earlier layer), so it escalates. Layers and :where() solve the same problem; !important stays for utilities and overriding third-party inline styles.
  • What is the specificity of nav ul li a.active:hover? (0, 2, 4). Of #main :not(.x) p? (1, 1, 1).

Common Mistakes​

  • "Specificity is a single number: id 100, class 10." The columns never overflow into each other.
  • "A higher layer loses to more specific selectors in a lower layer." Layer order is checked before specificity.
  • "Inline styles have specificity (1, 0, 0, 0)." They are a separate step of the cascade; the result is the same, but it explains why layers do not affect them.
  • Expecting !important in a later layer to win: with !important the earlier layer wins.

See Also​