Specificity & the Cascade, Deeply

Score selectors like the browser does, understand where @layer and !important sit, and stop fighting your own CSS.

The intro course taught the pecking order. Let's make it precise, because “why is this style not applying?” is the single most common CSS frustration, and it's almost always a specificity story.

Selectors score as a triple (a, b, c)

  • a — number of ID selectors.
  • b — number of classes, attribute selectors, and pseudo-classes.
  • c — number of element and pseudo-element selectors.

Compare left to right: (1,0,0) beats (0,9,9). An ID always outranks any number of classes. That's why a stray #sidebar can be so hard to override.

The full cascade order (highest wins)

  1. Transition declarations (during a transition).
  2. !important — and here the layer order reverses.
  3. Cascade layers (@layer) — later layers win among normal declarations.
  4. Specificity, as scored above.
  5. Source order — last one wins on a tie.

Notice specificity is only step four. Layers sit above it, which is exactly why they're such a clean way to tame third-party CSS.

Example

Try it yourself
Loading editor…
Press Run to see the result.

When to use it

  • A developer uses @layer to declare a third-party library in a lower layer so in-house styles always win without !important.
  • A developer calculates specificity scores (0-2-1 vs 1-0-0) to understand why an ID rule overrides a two-class selector.
  • A team establishes a layer order (reset, base, components, utilities) at the top of every project so style precedence is predictable and documented.

More examples

Specificity score examples

Shows four rules with increasing specificity scores to illustrate how the browser picks the winning declaration.

Example · css
/* 0-0-1 element */
p { color: gray; }

/* 0-1-0 class — wins over element */
.intro { color: #2965f1; }

/* 0-2-1 two classes + element — wins over single class */
nav.main-nav a { color: #e05c00; }

/* 1-0-0 ID — wins over all above */
#hero-title { color: #1a1a2e; }

@layer for predictable cascade control

Declares an explicit layer order so utilities always override components, which override base — no !important required.

Example · css
@layer reset, base, components, utilities;

@layer reset {
  *, *::before, *::after { box-sizing: border-box; margin: 0; }
}
@layer base {
  body { font-family: system-ui, sans-serif; }
}
@layer components {
  .btn { padding: 8px 16px; background: #2965f1; color: #fff; }
}
@layer utilities {
  .mt-8 { margin-top: 8px; }
}

Third-party library in a lower layer

Places an imported library in the external layer so all site-layer rules automatically override it by cascade order.

Example · css
@layer external, site;

@import url('bootstrap.css') layer(external);

@layer site {
  /* These rules always beat bootstrap without !important */
  .btn { border-radius: 0; }
}

Discussion

  • Be the first to comment on this lesson.