CSS
medium
mid
CSS specificity and the cascade — how does the browser pick which rule wins?
The cascade resolves conflicts by: origin/importance > specificity > source order. Specificity is a 3-tuple (IDs, classes/attrs/pseudo-classes, type/pseudo-elements). `!important` and inline styles short-circuit the normal flow.
6 min read·~10 min to think through
Browsers pick the winning declaration via this order:
- Origin & importance — author normal < author important < user important < UA important. (Yes,
!importantfrom the user wins over!importantfrom the author.) - Specificity — a 3-tuple (a, b, c):
- a = number of ID selectors (
#id) - b = number of class, attribute (
[type=text]), and pseudo-class (:hover) selectors - c = number of type (
div) and pseudo-element (::before) selectors
Compared lexicographically: (0,1,0) beats (0,0,99).
- Source order — among rules of equal weight and specificity, the later one wins.
Special cases:
- Inline
style="…"has specificity higher than any selector but loses to!importantfrom CSS. :where()has specificity 0 — perfect for low-spec resets.:is()takes the highest spec of its arguments.- Cascade layers (
@layer) override specificity within layer order — earlier-declared layers lose to later ones, regardless of specificity. This is the modern way to organize a stylesheet without specificity wars.
The practical advice: keep selectors flat, prefer one-class-per-rule, use CSS variables for theme variants, and reach for @layer over !important when you need to clearly stratify resets / components / utilities.
Code
Follow-up questions
- •How does `:where()` differ from `:is()` for specificity?
- •When is `!important` an acceptable choice?
- •How do CSS layers interact with framework styles (Tailwind, MUI)?
Common mistakes
- •Reaching for `!important` to win — usually an indicator of a deeper architecture problem.
- •Long descendant selectors that pile up specificity and trap you.
- •Treating inline styles as the same as a `style` selector — they're higher.
Performance considerations
- •Selector matching cost is rarely the bottleneck; descendant selectors used to matter more, modern engines are fast.
Edge cases
- •`@layer` + `!important` flips order — important declarations cascade in the *reverse* layer order.
- •Shadow DOM has its own scoped cascade; outer styles need `::part()` or CSS custom properties to influence it.
Real-world examples
- •Tailwind CSS uses layers (`@layer base/components/utilities`) under the hood — that's how `mt-4` overrides component defaults predictably.
Senior engineer discussion
Senior signal: discuss design-system layering strategy, scoped vs global CSS, the role of CSS variables for theming, and how Shadow DOM changes the cascade boundaries.