Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

CSS  Module 3 – The cascade, specificity and layers

The cascade: which declaration wins

How a browser picks one value when CSS declarations conflict: origins and importance, shadow trees, the style attribute, layers, specificity and order.

  • Intermediate
  • 30 minutes
  • Examples run with Node.js 24.21.0
  • By MySmartCoPilot
Free lesson, Pro answers: 30 minutes
Free: the whole lesson. With a pass: the model answers to its 2 interview questions.

What you will learn

  • List the cascade's sorting steps in order and say which step decides a given conflict
  • Predict the winner when browser, user and author styles conflict
  • Explain how !important reverses origin order and why it is a last resort

Before you start

What you’ll learn · Before you start

What you will learn

  • List the cascade's sorting steps in order and say which step decides a given conflict
  • Predict the winner when browser, user and author styles conflict
  • Explain how !important reverses origin order and why it is a last resort

Before you start

On this page

When two or more declarations give the same property different values on the same element, the browser keeps exactly one of them, and the process that chooses it is the cascade. It compares the competing declarations by asking six questions, always in the same order: where the declaration comes from and whether it is marked !important; whether it comes from the page or from a shadow tree; whether it sits in the element’s own style attribute; which cascade layer holds it; how specific its selector is; and which one appears last. The first question that tells two declarations apart decides between them, and the later questions are never asked. That order explains most “my CSS is being ignored” bugs. Specificity is only the fifth question, so a heavy selector can lose to a light one in a later layer, to a style attribute, or to any !important declaration. The rules come from CSS Cascading and Inheritance Level 5, and every conflict below is decided by a small cascade model whose output is recorded on this page.

What the cascade compares

Before anything is sorted, the browser collects the declared values for each property of each element. A declaration takes part only if its style sheet applies to the document, it is not inside an @media or @supports rule whose condition is false, its rule’s selector matches the element, and it is valid: the property name is known and the value fits that property’s grammar. A declaration such as color: 12px is thrown away while the style sheet is read, so it never competes and can never win.

The cascade works on single declarations, not on whole rules. In the pair below, the second rule wins color and has nothing to say about padding, so the card keeps the first rule’s padding:

Each property is decided on its own CSS · per-property.css
/* Both rules match <div id="main"><p class="card">…</p></div>. */
.card {
  color: red;
  padding: 1rem;
}

#main .card {
  color: blue;
}

/* Result: color comes from the second rule, which is more specific;
   padding comes from the first, because no other rule sets it. */

The cascade’s output is the list of declared values sorted from winner to loser. The first entry is the cascaded value. When no declaration applies at all, there is no cascaded value, and the property is filled in by inheritance or takes its initial value, which is the subject of the lesson on inheritance and computed values.

The six questions, in order

Six questions from top to bottom, a tie passing to the next: origin and importance, context, style attribute, layers, specificity, order.1. Origin and importancewhich origin, and is it !important?2. Contextthe page, or a shadow tree inside it?3. Style attributewritten on the element itself?4. Layerswhich cascade layer?5. Specificitywhose selector scores higher?6. Order of appearancewhich comes last?tietietietietie

The cascade asks six questions, in this order

Text description of the diagram

The diagram shows the six questions the cascade asks about two declarations of the same property on the same element, from top to bottom. The first question that separates them decides the winner; a tie sends the pair to the next question.

  1. Origin and importance: which origin the declaration comes from, and whether it is marked !important.
  2. Context: whether it comes from the page or from a shadow tree inside it.
  3. Style attribute: whether it is written in the element's own style attribute.
  4. Layers: which cascade layer it belongs to.
  5. Specificity: whose selector has the higher specificity.
  6. Order of appearance: which declaration comes last.
  1. Origin and importance. Declarations from the browser, the reader and the site rank differently, and !important changes that ranking. This question settles more conflicts than any other.
  2. Context. A web component’s shadow tree is a separate context. For normal declarations the page’s rule beats the component’s own; for !important ones the component’s rule wins.
  3. Style attribute. A declaration written in style="…" on the element beats every declaration of the same importance that reaches the element through a selector, however specific that selector is.
  4. Layers. Rules inside @layer blocks rank by the order of their layers, and rules outside every layer count as one more layer after all the others.
  5. Specificity. The selector with more IDs wins; on a tie, the one with more classes, attributes and pseudo-classes; then the one with more type selectors and pseudo-elements.
  6. Order of appearance. If everything else is equal, the declaration that comes later in the style sheets wins.

Two declarations that differ on question 1 are never compared on specificity. This is the misunderstanding behind the advice “use a more specific selector”: it works only when the competing declarations already tie on origin, importance, context, attachment and layer.

Origins: browser, reader and site

Every declaration has an origin, the place it enters the cascade from.

  • The user-agent origin is the browser’s default style sheet. The HTML Standard lists the styles a browser is expected to apply, such as li { display: list-item; }, a rule that gives elements with the hidden attribute display: none, and input[type=hidden i] { display: none !important; }.
  • The user origin holds styles the reader asks for: a user style sheet, an extension that injects one, or a browser setting that behaves like one.
  • The author origin is the site: its linked style sheets, its <style> elements and its style attributes.

CSS Animations and CSS Transitions add two more origins whose declarations exist only while an animation or a transition runs.

!important turns the order around

For normal declarations the site beats the reader, and the reader beats the browser. Marking a declaration !important raises it above every normal declaration and reverses the order of the three origins among important ones. The program below gives color one declaration from each origin, normal and important, and lets the cascade model in css-model/cascade.mjs sort them:

One declaration from each origin, sorted JavaScript · origins.mjs
// One declaration of `color` from each origin, normal and !important, sorted by the cascade model in
// ./css-model/cascade.mjs. Everything else about them is the same, so only origin and importance can decide.
import { cascade, originOf } from "./css-model/cascade.mjs";

const declarations = [
  { origin: "user-agent", important: false, value: "black" },
  { origin: "user-agent", important: true, value: "gray" },
  { origin: "user", important: false, value: "navy" },
  { origin: "user", important: true, value: "yellow" },
  { origin: "author", important: false, value: "teal" },
  { origin: "author", important: true, value: "crimson" },
  { origin: "animation", value: "orange" },
  { origin: "transition", value: "purple" },
];

cascade(declarations).forEach((d, i) => {
  console.log(`${i + 1}. ${originOf(d).padEnd(22)} color: ${d.value}`);
});

Output

1. transition             color: purple
2. important user-agent   color: gray
3. important user         color: yellow
4. important author       color: crimson
5. animation              color: orange
6. normal author          color: teal
7. normal user            color: navy
8. normal user-agent      color: black

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node origins.mjs

Read the result from the top. A running transition wins over everything, then come the important declarations of the browser, the reader and the site, in that order, then a running animation, then the normal declarations of the site, the reader and the browser.

The reversal is deliberate. The site normally decides how its pages look, but a reader who needs large text or particular colours can insist with !important in their own style sheet, and no rule of the site can override them, not even an important one. Above that, the browser can insist on the few things that must always hold: a hidden input is never shown, whatever a site says about it.

Animations sit between the two halves. While an animation runs, its value beats every normal declaration, which is why a keyframe can move an element that a selector-based rule tried to keep still. Any !important declaration still beats it, and inside @keyframes a declaration marked !important is invalid and ignored, so an animation cannot climb above important rules.

Ten conflicts, decided step by step

The program below builds a small page for each conflict, with a browser style sheet, a reader’s style sheet and the site’s style sheet where the case needs them, and asks the model for the winning declaration and the question that decided it. The model reads only the CSS these cases use; its six files are in the css-model folder beside the example, and cascade.mjs, shown after the output, is the part that sorts.

Ten conflicts and the question that settled each JavaScript · conflicts.mjs
// Ten conflicts, decided by the cascade model in ./css-model. Each case is a tiny page with its style sheets; the
// model prints the winning declaration, the one it beat, and the question of the cascade that decided between them.
import { decide } from "./css-model/cascade.mjs";
import { createPage } from "./css-model/index.mjs";

const cases = [
  {
    title: "The hidden attribute against a class",
    html: `<div class="card" hidden>Weekly digest</div>`,
    ua: `[hidden]:not([hidden=until-found i]):not(embed) { display: none }`,
    author: `.card { display: flex }`,
    element: ".card",
    property: "display",
  },
  {
    title: "An important browser rule against an important author rule",
    html: `<input type="hidden" class="debug">`,
    ua: `input[type=hidden i] { display: none !important }`,
    author: `.debug { display: block !important }`,
    element: ".debug",
    property: "display",
  },
  {
    title: "A reader's style sheet against the site's",
    html: `<p class="fine-print">Terms apply</p>`,
    user: `p { font-size: 24px !important }`,
    author: `.fine-print { font-size: 12px !important }`,
    element: ".fine-print",
    property: "font-size",
  },
  {
    title: "A style attribute against an ID selector",
    html: `<p id="intro" style="color: purple">Welcome</p>`,
    author: `#intro { color: red }`,
    element: "#intro",
    property: "color",
  },
  {
    title: "An important rule against a style attribute",
    html: `<p id="intro" style="color: purple">Welcome</p>`,
    author: `#intro { color: red !important }`,
    element: "#intro",
    property: "color",
  },
  {
    title: "A class in a later layer against an ID in an earlier layer",
    html: `<p id="intro" class="lede">Welcome</p>`,
    author: `@layer base, components;
             @layer components { .lede { color: green } }
             @layer base { #intro { color: red } }`,
    element: ".lede",
    property: "color",
  },
  {
    title: "Two selectors in the same layer",
    html: `<article class="card"><h2 class="title">News</h2></article>`,
    author: `.card .title { color: teal }
             .title { color: maroon }`,
    element: ".title",
    property: "color",
  },
  {
    title: "Equal specificity: the later rule wins",
    html: `<button class="btn danger">Delete</button>`,
    author: `.danger { background-color: crimson }
             .btn { background-color: navy }`,
    element: ".btn",
    property: "background-color",
  },
];

const report = (n, title, winner, loser, step) => {
  console.log(`${String(n).padStart(2)}. ${title}`);
  console.log(`    wins:       ${winner}`);
  console.log(`    loses:      ${loser}`);
  console.log(`    decided by: ${step}`);
};

cases.forEach((c, i) => {
  const sheets = [
    { origin: "user-agent", css: c.ua ?? "" },
    { origin: "user", css: c.user ?? "" },
    { origin: "author", css: c.author ?? "" },
  ];
  const page = createPage({ html: c.html, sheets });
  const { winner, runnerUp, step } = page.explain(page.query(c.element), c.property);
  report(i + 1, c.title, winner.source, runnerUp.source, step);
});

// Cases 9 and 10 need a shadow tree, which the page model does not build, so their declarations are written out:
// a component's own rule inside its shadow tree (context 1) and the page's rule for the same part (context 0).
for (const important of [false, true]) {
  const flag = important ? " !important" : "";
  const inner = { origin: "author", important, context: 1, specificity: [1, 0, 0], order: 1, source: `in the shadow tree: #label { color: gray${flag} }` };
  const outer = { origin: "author", important, context: 0, specificity: [0, 0, 2], order: 2, source: `on the page: user-card::part(label) { color: black${flag} }` };
  const { winner, loser, step } = decide(inner, outer);
  report(important ? 10 : 9, `A shadow tree's rule against the page's rule${important ? ", both !important" : ""}`, winner.source, loser.source, step);
}

Output

 1. The hidden attribute against a class
    wins:       .card { display: flex }
    loses:      user-agent: [hidden]:not([hidden=until-found i]):not(embed) { display: none }
    decided by: origin and importance
 2. An important browser rule against an important author rule
    wins:       user-agent: input[type=hidden i] { display: none !important }
    loses:      .debug { display: block !important }
    decided by: origin and importance
 3. A reader's style sheet against the site's
    wins:       user: p { font-size: 24px !important }
    loses:      .fine-print { font-size: 12px !important }
    decided by: origin and importance
 4. A style attribute against an ID selector
    wins:       style="color: purple"
    loses:      #intro { color: red }
    decided by: style attribute
 5. An important rule against a style attribute
    wins:       #intro { color: red !important }
    loses:      style="color: purple"
    decided by: origin and importance
 6. A class in a later layer against an ID in an earlier layer
    wins:       @layer components { .lede { color: green } }
    loses:      @layer base { #intro { color: red } }
    decided by: layers
 7. Two selectors in the same layer
    wins:       .card .title { color: teal }
    loses:      .title { color: maroon }
    decided by: specificity
 8. Equal specificity: the later rule wins
    wins:       .btn { background-color: navy }
    loses:      .danger { background-color: crimson }
    decided by: order of appearance
 9. A shadow tree's rule against the page's rule
    wins:       on the page: user-card::part(label) { color: black }
    loses:      in the shadow tree: #label { color: gray }
    decided by: context
10. A shadow tree's rule against the page's rule, both !important
    wins:       in the shadow tree: #label { color: gray !important }
    loses:      on the page: user-card::part(label) { color: black !important }
    decided by: context

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node conflicts.mjs

css-model/cascade.mjs: the six questions as code JavaScript · css-model/cascade.mjs
// The cascade of CSS Cascading and Inheritance Level 5 (§6.1): the questions a browser asks, in order, when several
// declarations set the same property on the same element. The first question on which two declarations differ
// decides between them; the later questions are never asked.
//
// A declaration here is a plain object:
//   { value, origin, important, context, attached, layer, specificity, order, source }
//   origin       "user-agent", "user", "author", "animation" or "transition"
//   important    true for a declaration marked !important
//   context      0 for the document, 1 for a shadow tree inside it, 2 for one inside that, and so on
//   attached     true for a declaration in a style attribute
//   layer        the position of its cascade layer in the layer order (unlayered declarations come last)
//   specificity  [A, B, C] of the rule's selector
//   order        its position in the style sheets, in the order the browser reads them

/** Origin and importance, highest first (§6.1). */
export const PRECEDENCE = [
  "transition",
  "important user-agent",
  "important user",
  "important author",
  "animation",
  "normal author",
  "normal user",
  "normal user-agent",
];

export const originOf = (d) => (d.origin === "transition" || d.origin === "animation" ? d.origin : `${d.important ? "important" : "normal"} ${d.origin}`);
const rank = (d) => PRECEDENCE.length - PRECEDENCE.indexOf(originOf(d));

const compareSpecificity = (x, y) => x[0] - y[0] || x[1] - y[1] || x[2] - y[2];

/**
 * The cascade's questions in order. Each returns a positive number when declaration a wins, a negative one when b
 * wins, and 0 when the question does not separate them.
 */
export const STEPS = [
  ["origin and importance", (a, b) => rank(a) - rank(b)],
  // Normal declarations: the outer context wins. Important ones: the inner context wins.
  ["context", (a, b) => (a.important ? 1 : -1) * ((a.context ?? 0) - (b.context ?? 0))],
  ["style attribute", (a, b) => Number(!!a.attached) - Number(!!b.attached)],
  // Normal declarations: the later layer wins. Important ones: the earlier layer wins.
  ["layers", (a, b) => (a.attached || b.attached ? 0 : (a.important ? -1 : 1) * ((a.layer ?? Infinity) - (b.layer ?? Infinity)) || 0)],
  ["specificity", (a, b) => (a.attached || b.attached ? 0 : compareSpecificity(a.specificity ?? [0, 0, 0], b.specificity ?? [0, 0, 0]))],
  ["order of appearance", (a, b) => (a.order ?? 0) - (b.order ?? 0)],
];

/** Which of two declarations wins, and the question that decided it. */
export function decide(a, b) {
  for (const [step, test] of STEPS) {
    const result = test(a, b);
    if (result > 0) return { winner: a, loser: b, step };
    if (result < 0) return { winner: b, loser: a, step };
  }
  return { winner: a, loser: b, step: "nothing (they are the same declaration)" };
}

/** Sorts declarations from the winner down: the cascade's output (§6.1). */
export function cascade(declarations) {
  return [...declarations].sort((a, b) => (decide(a, b).winner === a ? -1 : 1));
}

The model is small, but on these pages it agrees with real browser engines: apart from case 3, which needs a reader’s own style sheet, Chromium 153, Firefox 155 and WebKit 26.6 give the same winner in every case.

The cases that surprise people most are these.

  • Case 1 is a real and common bug. A designer adds hidden to a card, the card stays on screen, and the reason is the origin: the browser’s rule for [hidden], copied in the case from the HTML Standard, is a normal user-agent declaration, and the site’s .card { display: flex } is a normal author declaration, which outranks it whatever the selectors are. Bootstrap’s Reboot style sheet, for one, adds [hidden] { display: none !important; } to the site’s own CSS for exactly this reason.
  • Case 2 shows the other end of the ladder: an important browser rule beats an important site rule, so a hidden input cannot be shown with CSS at all.
  • Cases 4 and 5 show the style attribute sitting between the two importance levels. It beats an ID selector, because question 3 comes before specificity, and it loses to any !important rule of the site, because question 1 comes first.
  • Case 6 is the reason cascade layers exist: a one-class selector beats an ID selector because its layer comes later, and specificity never gets a say.
  • Cases 9 and 10 involve a web component. Its shadow tree styles the inner #label element; the page styles the same element through user-card::part(label), a pseudo-element the component exposes on purpose. For normal declarations the page wins, so a component’s internal styles act as defaults that the page can change. For !important declarations the component wins, so a component can insist on something the page cannot undo.

Why !important is a last resort

!important is not a stronger kind of specificity. It moves a declaration to a different rung of the first question, and once it is there, only another important declaration can beat it, decided by the same questions all over again. A codebase that answers one !important with another ends up comparing important declarations by specificity and order, exactly the fight it was trying to escape, and every later fix needs !important too.

There are good uses. A rule whose whole job is to override, such as a .visually-hidden utility or a [hidden] reset, can reasonably be important, and a reader’s own style sheet is the reason the feature exists. For ordinary conflicts, the questions that come after origin are better tools:

  • lower the specificity of the rule that should lose, for example by replacing an ID with a class or by wrapping part of a selector in :where(), which counts for nothing;
  • put the two rules in cascade layers in the order you want them to win;
  • move the rule that should win later in the source, when the specificities are equal.

Order of appearance, across files

The last question compares positions in the combined style sheets. The browser reads style sheets in the order the document links them, <link> and <style> elements alike, and an @import counts as if the imported file were pasted where the @import rule stands. Declarations in style attributes count as coming after every style sheet, although question 3 has usually decided before position matters. Two consequences follow. Moving a <link> element can change how a page looks without any CSS changing, and in a component library the order of the files is part of its interface: a modifier class written before its base class loses every tie.

Version note

CSS Cascading and Inheritance Level 6 adds one more question between specificity and order of appearance: scope proximity. When two rules come from different @scope blocks, the one whose scope root is fewer steps up the tree from the element wins. The module on custom properties and architecture covers @scope.

Exercise: let the dark theme win

The panels below were styled years before anyone added a dark theme. The theme’s title and note should be white and silver, but they stay navy and slate grey.

The style sheet to fix, and the page

exercise/component.css

/* Panels, written first. */
#app .panel .panel-title { color: navy; }
#app .panel .panel-note { color: slategray; }

/* The dark theme, added later: its title and note should be white and silver. */
.theme-dark { background-color: black; }
.theme-dark .panel-title { color: white; }
.theme-dark .panel-note { color: silver; }

exercise/page.html

<main id="app">
  <section class="panel">
    <h2 class="panel-title">Orders</h2>
    <p class="panel-note">3 waiting</p>
  </section>
  <section class="panel theme-dark">
    <h2 class="panel-title">Payments</h2>
    <p class="panel-note">All settled</p>
  </section>
</main>

Change only component.css so that every sample test passes, without !important and without any selector more specific than (0, 2, 0). check.mjs reads the page and the style sheet with the same cascade model; run it with node check.mjs in the exercise folder after each change, or paste the two files into the playground below and read the computed colours in DevTools. As given, it fails three of its seven tests and says which rule won each failed one and why:

The sample tests, run on the style sheet as given JavaScript · exercise/check.mjs
// Sample tests for component.css: change the style sheet, then run `node check.mjs` in this folder.
// The page and the style sheet are read by the cascade model in ../css-model, which knows the CSS this exercise needs.
import { readFileSync } from "node:fs";
import { createPage } from "../css-model/index.mjs";
import { compareSpecificity, formatSpecificity, specificity } from "../css-model/selectors.mjs";
import { parseStyleSheet } from "../css-model/stylesheet.mjs";

const css = readFileSync("component.css", "utf8");
const page = createPage({ html: readFileSync("page.html", "utf8"), sheets: [{ origin: "author", css }] });
const light = "section:not(.theme-dark)";
const dark = "section.theme-dark";

const expectations = [
  ["the light panel's title is navy", `${light} .panel-title`, "color", "navy"],
  ["the light panel's note is slategray", `${light} .panel-note`, "color", "slategray"],
  ["the dark panel is black", dark, "background-color", "black"],
  ["the dark panel's title is white", `${dark} .panel-title`, "color", "white"],
  ["the dark panel's note is silver", `${dark} .panel-note`, "color", "silver"],
];

let failed = 0;
const result = (ok, name, detail = "") => {
  if (!ok) failed++;
  console.log(`${ok ? "pass" : "FAIL"}  ${name}${detail}`);
};

for (const [name, selector, property, want] of expectations) {
  const el = page.query(selector);
  const got = page.computed(el, property);
  if (got === want) {
    result(true, name);
    continue;
  }
  const { winner, runnerUp, step } = page.explain(el, property);
  const why = winner && runnerUp ? `; ${winner.source} beat ${runnerUp.source} on ${step}` : "";
  result(false, name, `: got ${got}${why}`);
}

const declarations = parseStyleSheet(css);
const important = declarations.filter((d) => d.important);
result(important.length === 0, "no declaration uses !important", important.length ? `: ${important.length} do` : "");
const limit = [0, 2, 0];
const tooStrong = [...new Set(declarations.flatMap((d) => d.selectors).filter((s) => compareSpecificity(specificity(s), limit) > 0).map((s) => `${s.text} ${formatSpecificity(specificity(s))}`))];
result(tooStrong.length === 0, `no selector is more specific than ${formatSpecificity(limit)}`, tooStrong.length ? `: ${tooStrong.join(", ")}` : "");

console.log(`${expectations.length + 2 - failed} of ${expectations.length + 2} tests pass`);
process.exitCode = failed ? 1 : 0;

Output (exit status 1)

pass  the light panel's title is navy
pass  the light panel's note is slategray
pass  the dark panel is black
FAIL  the dark panel's title is white: got navy; #app .panel .panel-title { color: navy } beat .theme-dark .panel-title { color: white } on specificity
FAIL  the dark panel's note is silver: got slategray; #app .panel .panel-note { color: slategray } beat .theme-dark .panel-note { color: silver } on specificity
pass  no declaration uses !important
FAIL  no selector is more specific than (0, 2, 0): #app .panel .panel-title (1, 2, 0), #app .panel .panel-note (1, 2, 0)
4 of 7 tests pass

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node check.mjs

First hint: the theme’s rules already come later, so they win as soon as the two sides tie on specificity. Which part of the panel selectors makes them stronger than they need to be?

HTML, CSS & JS Playground (Online Editor) Paste the page and the style sheet to see the conflict live, and watch DevTools' Styles panel cross out the losing declarations.

Self-check

Check yourself

7 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.

  1. Question 1 of 7 Put the cascade's questions in the order the browser asks them.

    Give each item its position, from 1 (first).

    Show the answer to question 1

    Answer:

    1. Origin and importance
    2. Context (page or shadow tree)
    3. Style attribute
    4. Cascade layers
    5. Specificity
    6. Order of appearance

    CSS Cascading and Inheritance Level 5 sorts declarations by origin and importance, then context, then whether the declaration is attached to the element through its style attribute, then layers, then specificity, and only then by order of appearance. The first question on which two declarations differ decides.

  2. Question 2 of 7 A reader's own style sheet says p { font-size: 20px; }. The site says .note { font-size: 14px; }. Neither is !important. What font size does <p class="note"> get?

    Choose one answer.

    Show the answer to question 2

    Answer: 14px: normal author declarations beat normal user declarations

    The two declarations differ on the first question, origin, so specificity is never compared. Among normal declarations the site's beat the reader's. Had both been !important, the reader's 20px would win.

  3. Question 3 of 7 Both declarations of the previous question are now marked !important. Which wins?

    Choose one answer.

    Show the answer to question 3

    Answer: The reader's 20px

    For important declarations the origin order reverses: important user declarations beat important author declarations. That is what lets a reader enforce large text on any site.

  4. Question 4 of 7 An element has style="color: purple". Which of these can still decide its colour instead?

    Choose every answer that is right.

    Show the answer to question 4

    Answer:

    • An author rule #intro { color: red !important; }
    • A reader's rule p { color: black !important; }
    • A running CSS animation that changes color

    The style attribute is a normal author declaration that wins question 3. Anything that wins question 1 beats it: any important declaration (from the site, the reader or the browser) and a running animation, which ranks above normal author declarations. A selector, however specific, never does, because the style attribute is compared before specificity.

  5. Question 5 of 7 @layer base, components; comes first. Then @layer base { #intro { color: red; } } and @layer components { .lede { color: green; } }. What colour is <p id="intro" class="lede">?

    Choose one answer.

    Show the answer to question 5

    Answer: Green: the components layer comes later in the layer order

    Layers are compared before specificity. For normal declarations the later layer wins, so (0, 1, 0) in components beats (1, 0, 0) in base. The source order of the two blocks does not matter here: the @layer statement fixed the order of the layers.

  6. Question 6 of 7 What happens to color: red !important written inside a @keyframes rule?

    Choose one answer.

    Show the answer to question 6

    Answer: It is invalid and ignored

    CSS Animations Level 1 says that properties marked !important inside keyframes are invalid and ignored. An animation's values come from the animation origin, which is above normal declarations and below important ones.

  7. Question 7 of 7 Why is this card visible even though it has the hidden attribute?

    Read the code, then choose one answer.

    <div class="card" hidden>Offer</div>
    <style>
      .card { display: flex; }
    </style>
    Show the answer to question 7

    Answer: The browser's rule for [hidden] is a normal user-agent declaration, and any normal site rule for display beats it

    The browser's rule for [hidden] and the site's .card rule come from different origins, so their specificities are never compared: normal author declarations outrank normal user-agent ones. A site rule such as [hidden] { display: none !important; } restores the expected behaviour.

Interview questions

What steps does the cascade use to decide which declaration wins? It compares declarations for the same property on the same element in a fixed order: origin and importance, then context (the page or a shadow tree), then whether the declaration is in the element’s style attribute, then cascade layers, then specificity, and finally order of appearance. The first step where two declarations differ decides, so specificity matters only when origin, importance, context, style attribute and layer all tie. Among normal declarations the site beats the reader and the reader beats the browser; !important reverses that order and puts every important declaration above normal ones.

Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.

  1. Fresher to mid

    A product card has the hidden attribute but still shows on the page, and DevTools shows display: flex on it. Explain why the browser allows this, then fix it in two ways and say which one you would ship in a design system.

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Mid to senior

    A ten-year-old stylesheet has 140 !important declarations, and every new fix adds another. How do you remove them without visual regressions, and which ones would you keep?

    Model answer, Pro. First step and follow-ups

Key takeaways

  • The cascade sorts the valid declarations for one property on one element; invalid declarations never compete.
  • Six questions in a fixed order: origin and importance, context, style attribute, layers, specificity, order of appearance. The first difference decides.
  • Normal declarations: site over reader over browser. Important declarations: browser over reader over site, all above normal ones; transitions top everything and animations sit between the two halves.
  • A style attribute beats any selector of the same importance and loses to any !important rule of the site.
  • !important is a move to another rung, not a bigger specificity; prefer lower specificity, layers or source order for ordinary conflicts.
  • The browser’s rule that hides [hidden] elements is a normal declaration, so any site rule that sets display on the element defeats it.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.