Most CSS style guides tell you to style a component with a single class like `.card__title` instead of a chain like `#main .card h2`. Both match the same heading — what does the flat single-class convention buy a growing codebase?
answer
- weight is a one-way ratchet
- chains encode markup, classes encode meaning
- one ID outranks any number of classes
- ties are broken by source order
- order is a decision you control
basics
~20 sA single class keeps every rule at the same low weight, so a later rule can override it by source order alone. IDs and long descendant chains raise the weight, forcing every future override to be heavier still.
solid answer
~40 sBoth selectors match, but they do not carry the same weight in the cascade. `#main .card h2` counts an ID, a class and a type; `.card__title` counts one class. Whenever the two set the same property, the chain wins regardless of which is written later, so the next person who needs a different colour has to write something even heavier — and the person after that, heavier again. Keeping every component rule at single-class weight means nothing outweighs anything: conflicts are decided by source order, which is something the team actually controls through file and layer order. It also decouples the rule from the markup, so the style survives when the heading moves out of `#main` or stops being an `h2`.
code
css · 5 lines.card__title { color: #333; }
.card__title--featured { color: crimson; }
#main .card h2 { color: #333; }
.card h2 { color: crimson; }go deeper
Be able to say plainly that a chain like #main .card h2 outweighs a single class and wins regardless of order, and that styling through one class keeps rules interchangeable.
Explain the ratchet: because nothing lowers an existing rule's weight, every override written to win raises the floor for the next author. Show how flat rules turn conflicts into an ordering question.
Demonstrate that you enforce this rather than merely prefer it — a defined layer or file order, a linted ceiling on selector weight, and a stated exception for content you do not author.
Own the tradeoff: flatness moves the decision from selector authoring into ordering, which only pays off if the order is published, stable and owned. Be ready to say who arbitrates the order across teams.
## The two selectors are not interchangeable `#main .card h2` and `.card__title` can match the same element, but the cascade does not treat them as equals. Specificity is counted in three separate buckets — ID selectors, then class/attribute/pseudo-class selectors, then type and pseudo-element selectors — and the buckets are compared in that order. The chain contributes one ID, one class and one type. The flat class contributes one class and nothing else. Whenever both rules declare the same property on the same element, the chained rule wins, and it wins no matter where either rule sits in the file. That single fact is the whole argument. Once a heavy rule exists, the only way to beat it is to write something heavier. ## The ratchet CSS has no mechanism that lowers a rule's weight after the fact. So in a codebase where authors are free to reach for whatever selector wins today, the average weight only ever climbs: ```css /* Escalating: each override has to out-weigh the last */ #main .card h2 { color: #333; } .card h2 { color: crimson; } /* ignored */ #main .card h2.is-featured { color: crimson; } /* had to escalate */ /* Flat: source order decides, so ordering the files decides */ .card__title { color: #333; } .card__title--featured { color: crimson; } /* later rule wins */ ``` In the flat version the second rule wins simply because it comes later and ties on weight. That is not a weaker guarantee — it is a guarantee you can administer. File order, import order or cascade-layer order is a decision a build makes deterministically; "whose selector is longer" is not. ## What flat authoring means in practice - **One class per component rule.** `.card__title`, not `.card .title` and not `#main .card h2`. - **State is another class on the same element** — `.menu.is-open` — rather than an ancestor condition like `body.nav-open .menu`. - **Variants are modifier classes**, not deeper nesting. - **IDs are for fragment links, `label`'s `for` attribute and script hooks**, not for styling. A single ID outranks any number of classes, so one ID rule can quietly become the hardest thing in the file to override. - **Watch nesting.** Native CSS nesting and preprocessors both expand into descendant chains; three levels of visual indentation is three levels of specificity you did not consciously choose. ## The second benefit: decoupling from markup `#main .card h2` encodes three facts about the document — that the card lives inside `#main`, that the title is a descendant of the card, and that it is an `h2`. Every one of those is a thing a future markup change can break, and none of them is what you meant. `.card__title` says only "this is a card title". Move the card into a sidebar, change the heading to an `h3` for document outline reasons, reuse the card in a modal — the style follows the class because the class travels on the element. ## What it does not solve Flat specificity does not stop collisions. Two single-class rules that both set `color` on the same element still conflict; the later one wins. What changes is the nature of the problem: instead of an unresolvable arms race, you have an ordering question, and ordering is something a stylesheet architecture can define once — reset first, then base elements, then components, then utilities and overrides last. It also does not make specificity irrelevant. You still need to know how it computes to read someone else's stylesheet, and to understand why the rule you just wrote is being ignored. ## When a chain is still correct Combinators are not banned. Reach for one when you genuinely cannot put a class on the element: - content you do not author, such as rendered Markdown: `.prose h2` - structural relationships that are the point of the rule: `.stack > * + *` for spacing between siblings - markup produced by a third-party widget The useful test is intent. If the chain exists because the element cannot carry a class, it is fine. If it exists because you needed to win a fight with another rule, you have just raised the floor for everyone who comes after you.
- If two single-class rules set the same property on the same element, what decides the winner?Source order within the same origin and importance: the rule that appears later in the final stylesheet wins. That is exactly why flat authoring is paired with a defined order — reset, base, components, then overrides — usually enforced by import order or cascade layers, so the winner is a build-time decision rather than an accident of which file happened to load last.
- You need to style a heading inside HTML rendered from Markdown, where you cannot add classes. Does that break the convention?No. The convention is about not raising weight to win fights, not about banning combinators. Content you do not author is the standard exception: scope it once with something like `.prose h2` and accept the type selector. Keep the entry point a single class so the whole block can still be overridden or nested predictably, and confine the pattern to that region.
- Does using a shallower selector make the page render faster?Not meaningfully. Selector matching is highly optimised and matching cost is almost never the bottleneck in real pages. The argument for flat selectors is maintainability — override cost and coupling to markup — not speed. Claiming performance as the reason is a common tell that someone has repeated the advice without understanding it.
saying these in an interview costs you the question
- Thinks the rule written later always wins
- Says IDs are faster or slower to match, missing the real reason
- Believes deeper nesting means better organised CSS
- Treats specificity as one number rather than ordered buckets
- Thinks !important is a normal way to override a chain