skip to content

On a large team, CSS overrides have escalated until many declarations end in `!important`. What drives that escalation, and what change in how rules are authored actually stops it?

level: middleimportance: must knowfreq 55%

answer

  1. nothing ever lowers an existing rule's weight
  2. the cheapest override is a heavier one
  3. remove the reason, not the symptom
  4. let position win instead of weight
  5. one sanctioned home, linted everywhere else

basics

~20 s

Nothing in CSS lowers an existing rule's weight, so the cheapest way to override is always to write something heavier, and the floor keeps rising until it hits !important. The fix is authoring every rule at the same low weight and deciding conflicts by a defined order instead.

solid answer

~50 s

It is a ratchet. When an author has to override a rule they cannot edit, the fastest move is a heavier selector; the next author faces that heavier baseline and escalates again. `!important` is the top of the ladder, and once it is there the only answers are another `!important` or an inline one — so it spreads. The cure is not discipline about `!important` itself, it is removing the reason to reach for it: keep component rules at single-class weight so nothing outweighs anything, and make conflicts resolve by a defined order — cascade layers or a fixed file order — so overrides win by *where* they are, not by *how heavy* they are. Then leave one sanctioned, documented use of `!important` — typically utilities that must win by definition — and lint everything else.

code

css · 9 lines
css
@layer reset, base, components, utilities;

@layer components {
  #app .toolbar button.primary { background: navy; }
}

@layer utilities {
  .bg-brand { background: rebeccapurple; }
}

go deeper

for a junior

Recall that !important outranks normal declarations whatever the selector, and that reaching for it forces every later variant to use it too. Say you would look for why the existing rule wins first.

for a middle

Explain the escalation mechanism and name the concrete alternative: flat single-class rules plus a defined order — cascade layers or fixed import order — so overrides win by position rather than weight.

for a senior

Show how you would enforce it on a live codebase: a linted specificity ceiling, one allowlisted home for importance, and a migration path that does not require freezing feature work.

for a principal

Frame it as an ownership problem. Decide who publishes the layer order, how a team requests an exception, and what the escape hatch is when a vendor stylesheet you cannot edit lands in production.

## Why it is a ratchet and not a one-off mistake CSS gives you no way to reduce the weight of a rule you cannot edit. If a heavy rule is already winning and you need a different value, your realistic options are: edit the original rule, load your rule at a place that gives it precedence, or make your selector heavier. In a large codebase the first is often blocked — the rule belongs to another team, ships in a vendor stylesheet, or is generated — and the second is only available if load order is actually defined and known. So the third wins by default. The next author inherits a heavier baseline and does it again. `!important` is where the ladder ends, because an important author declaration beats every normal author declaration regardless of selector weight, including the `style` attribute. Once a base rule carries it, every legitimate variant of that component must carry it too. That is the mechanism by which one pragmatic fix becomes a file where most lines shout. ## The four things that push people onto the ladder 1. **Unownable rules.** Third-party widget CSS, another team's component, styles injected at runtime. 2. **Unknown load order.** If nobody can say which stylesheet ends up last, raising weight is the only override that is deterministic. Weight is a property of the rule; order is a property of the build. 3. **State written as ancestry.** `body.nav-open .menu .item` is three levels deep before you have styled anything, and every override of it must be deeper still. 4. **Nesting on autopilot.** Preprocessor and native nesting both expand into descendant chains. Visual indentation quietly becomes cascade weight. ## What actually stops it **Flatten the authoring.** Every component rule at single-class weight. State as a class on the same element (`.menu.is-open`), variants as modifier classes. When nothing outweighs anything, nobody needs a heavier selector, because a later rule already wins. **Make order the tiebreaker, and publish it.** Flat weight only helps if the order is deliberate. Cascade layers give you an explicit ordering that is independent of selector weight and of the order files happen to load in: ```css @layer reset, base, components, utilities; ``` A normal declaration in a later-declared layer beats one in an earlier layer no matter how heavy the earlier selector is — which is exactly the escape hatch escalation was substituting for. Layers shipped across the major browsers in 2022 (Chrome 99, Firefox 97, Safari 15.4). Without layers, the same effect comes from a fixed import order, which is more fragile but works. **Give defaults zero weight.** Rules an author expects to be overridden — a design system's base styling, a reset — can be wrapped so they contribute nothing to the comparison, letting any single class beat them. **Give overrides a home.** Most `!important` in the wild is someone with nowhere legitimate to put an override. A designated top layer or override file removes the excuse. **Lint the ceiling.** stylelint's `selector-max-specificity` caps how heavy a selector may be, and `declaration-no-important` bans importance outright — usually applied everywhere except one allowlisted file. A cap that is enforced in CI is what turns the convention from advice into a property of the codebase. ## The uses of !important that are not defects - **Utility classes that must win by definition.** `.is-hidden { display: none !important; }` is a deliberate, documented convention in most utility vocabularies. - **Beating markup you cannot edit**, including inline `style` attributes written by a third-party script, since an important author declaration outranks a normal inline one. - **Print and accessibility overrides** that intentionally trump everything. The distinction that matters in review is not whether `!important` appears but whether it is a bounded convention with a name and a home, or an ad-hoc fix in the middle of a component file. ## The interaction worth knowing Importance inverts layer order. Among author declarations, `!important` in an *earlier*-declared layer beats `!important` in a later one, and unlayered important declarations rank lowest of all. That is deliberate: it lets a low-level layer state a genuinely non-negotiable rule. It also means "add `!important`" is no longer a reliable universal trump once layers are in play, which is another reason to stop treating it as the default escape hatch.

  • A teammate argues that banning !important outright is enough. Why is that not the fix on its own?
    Because it removes the symptom while leaving the pressure. If overrides still have to win on weight, people will write ever-heavier selectors instead — which is harder to spot in review than an `!important` and just as hard to unwind. Bans work only once there is a legitimate, easy way to win: flat rules plus a defined layer or file order.
  • Where would you allow !important deliberately?
    In a small, named set of places: utility classes whose whole contract is to win, overrides of inline styles written by third-party scripts you cannot edit, and print or accessibility stylesheets. Each gets its own file, is documented, and is the only path allowlisted by the linter. Everywhere else the rule is off.
  • How would you actually detect that a codebase is on the ratchet?
    Measure rather than argue. Lint with a specificity cap and count violations, chart the specificity of selectors in source order to see where later rules are weaker than earlier ones, and count `!important` declarations per component. A rising count of important declarations concentrated in feature code, not in a utilities file, is the clearest signal.

saying these in an interview costs you the question

  • Says !important is fine as long as you use it sparingly
  • Solves an override by adding another ancestor to the selector
  • Thinks inline styles always beat stylesheet rules
  • Believes banning !important alone fixes the problem
  • Assumes the last stylesheet loaded always wins

context