skip to content

Why do CSS resets apply `box-sizing: border-box` through `*, *::before, *::after` rather than on `html` alone, and when would you use the `box-sizing: inherit` variant instead?

level: seniorimportance: should knowfreq 45%

answer

  1. not an inherited property
  2. the universal selector misses generated boxes
  3. zero specificity by design
  4. forcing inherit adds an escape hatch
  5. third-party widgets assume content-box

basics

~20 s

box-sizing is not an inherited property, so setting it on html alone changes nothing else; the reset must match every element, and the universal selector does not match pseudo-elements, hence ::before and ::after. The inherit variant lets a whole subtree be flipped back by one declaration on its root.

solid answer

~40 s

`box-sizing` is a **non-inherited** property whose initial value is `content-box`, so a single rule on `html` leaves every descendant untouched — the reset has to actually match each element. It lists `::before` and `::after` explicitly because the universal selector matches elements, and generated-content boxes are not elements. The variant `html { box-sizing: border-box } *, *::before, *::after { box-sizing: inherit }` gives the same result for the whole page but adds an escape hatch: because everything now *inherits*, setting `box-sizing: content-box` on one container flips that entire subtree in a single declaration — useful when a third-party widget was authored against `content-box`. The plain reset would need a rule matching the widget root and all of its descendants instead.

code

css · 8 lines
css
/* Plain reset: every element is set independently */
*, *::before, *::after { box-sizing: border-box; }

/* Opting a subtree out needs a descendant selector */
.legacy-widget,
.legacy-widget *,
.legacy-widget *::before,
.legacy-widget *::after { box-sizing: content-box; }

go deeper

for a junior

Know the reset line itself and that it makes width include padding and border for the whole page. Say that box-sizing is not inherited, which is why the rule has to match every element.

for a middle

Explain that the universal selector does not match generated-content boxes, that the reset carries zero specificity so components can override it, and what the inherit variant computes to.

for a senior

Show the integration judgment: name the third-party-widget failure mode, scope the override to the offending subtree, and prefer cascade layers over !important when you cannot edit foreign CSS.

for a principal

Own the convention as a contract across teams and vendored code: decide where the reset lives in layer order, whether shipped components declare their own sizing rather than relying on the host, and document the escape hatch so nobody invents !important.

## What the reset is The rule near the top of nearly every stylesheet is: ```css *, *::before, *::after { box-sizing: border-box; } ``` It exists because the CSS default, `content-box`, makes `width` describe only the innermost box, so padding and border push elements past their declared size and percentage layouts overflow. `border-box` matches how most people think about a box, so teams opt the whole document into it. ## Why not just `html { box-sizing: border-box }` Because `box-sizing` is **not an inherited property**. CSS splits properties into inherited ones (`color`, `font-family`, `line-height`) and non-inherited ones (`display`, `border`, `box-sizing`); a non-inherited property takes its **initial** value on every element where nothing declares it. Setting it on `html` therefore styles exactly one element — the root — and every descendant still computes `content-box`. Only a selector that *matches* each element can change them all, and that is what `*` is for. ## Why `::before` and `::after` are listed The universal selector matches **elements**. Pseudo-elements are not elements: they are boxes the engine generates for a `content` declaration, and no simple selector reaches them implicitly. If you omit them, a decorative `::after` with padding and a border sizes under `content-box` while its host sizes under `border-box`, producing exactly the one-off misalignment that is miserable to debug. Listing the two common generated-content pseudo-elements keeps the whole box tree on one model. ## Specificity: the reset is deliberately weak The universal selector contributes **zero** to specificity, so `*` has specificity 0-0-0 and `*::before` only counts the pseudo-element (0-0-1). Any class rule beats it. That is a feature: the reset is a default, not a mandate, and any component can opt out with a normal rule. It is also why the reset does not need `!important` — reaching for that is a red flag, since it converts a friendly default into something a component author cannot escape. A related myth worth rebutting: the universal selector is not a performance problem. Selector matching in modern engines is right-to-left and heavily optimised, and one declaration on every element is not a measurable cost. ## The `inherit` variant and what it buys ```css html { box-sizing: border-box; } *, *::before, *::after { box-sizing: inherit; } ``` The computed result for a clean page is identical: the root is `border-box`, and every element explicitly inherits from its parent, so the value cascades down the tree. The difference is what happens when you want to *stop* it. With the plain reset, every element was independently set to `border-box`, so flipping a subtree back means matching every element in it: ```css .legacy-widget, .legacy-widget * { box-sizing: content-box; } ``` With the `inherit` variant, one declaration on the subtree root is enough, because the descendants inherit from it: ```css .legacy-widget { box-sizing: content-box; } /* whole subtree follows */ ``` That is the entire argument. The trade is a small extra indirection — a reader has to notice the property is being forced to inherit — in exchange for a clean per-subtree override. Codebases that never integrate foreign markup usually keep the plain reset; codebases that embed third-party widgets, editor output, or legacy pages benefit from the inheritable one. ## When the reset causes trouble The realistic failure is a third-party widget whose stylesheet was authored assuming `content-box`. Its author wrote `width: 240px; padding: 12px` expecting a 264px panel; your global reset silently makes it 240px, and the internals cramp or clip. The targeted fix is to restore `content-box` for that subtree only, using whichever of the two forms your reset supports. Two further options are worth naming: putting the reset inside a cascade layer so widget styles that live in a later layer win regardless of specificity, or — where the widget is genuinely hostile to the host page — letting it render inside its own document. A second, quieter effect: some user-agent stylesheets set `box-sizing` on particular form controls, so a `*` reset also normalises those, which is usually why inputs and text areas finally line up with everything else after the reset lands. ## Answering it Lead with the fact that drives everything — `box-sizing` is not inherited — then the pseudo-element detail, then the specificity-zero point, and finish with the `inherit` variant framed as a per-subtree escape hatch rather than as a better default.

  • Does the universal selector in that reset hurt performance?
    No, in any measurable sense. Modern engines match selectors right-to-left and handle a universal match efficiently; a single non-inherited declaration on every element is not where stylesheet cost lives. The historical advice against `*` predates today's engines. Real CSS performance problems come from expensive layout and paint work, not from one universal declaration.
  • How would you keep the reset from fighting a third-party stylesheet you cannot edit?
    Put the reset in a cascade layer that sits earlier than the layer holding third-party styles, so their rules win on layer order rather than on specificity. Alternatively restore `content-box` on the widget subtree. Both are better than `!important`, which removes the component author's ability to override anything.
  • If the reset already sets `border-box` everywhere, why do component styles still declare it explicitly sometimes?
    Because a distributable component cannot assume the host page has a reset. Declaring `box-sizing: border-box` on the component's own root and internals makes its sizing self-contained, so it renders identically in a page with the reset, without it, or with a `content-box` subtree override above it.

saying these in an interview costs you the question

  • Believes box-sizing inherits, so html alone is enough
  • Adds !important to the reset to make it stick
  • Thinks the universal selector is a performance problem
  • Says ::before and ::after are matched by * anyway
  • Claims the inherit variant renders differently on a clean page

context