CSSTidy CSS Formatter

CSS Resets in 2026: modern-normalize, @layer, and What Actually Needs Resetting

CSSTidy — CSS Formatter & Minifier Guides · Updated 2026-10-12 · All guides

A reset has one job left: make the places where browsers still disagree agree. Everything else it used to do is now either unnecessary or actively harmful.

What UA styles already fixed

The blanket * { margin: 0; padding: 0 } era assumed every default was noise. Current engines give you sensible defaults that a blunt reset destroys:

  • dialog and popover come with focus handling and a backdrop; all: unset on them removes the centering you thought you were resetting.
  • Form controls inherit color and background from the system scheme when you set color-scheme; resetting them to white on a dark page is the classic "why is the date picker white" bug.
  • prefers-reduced-motion is not honoured by any reset; that has always been your problem.
  • Headings inside section/article still get level-correct sizing, so zeroing all font-size: inherit means your document outline is now invisible in the rendered page.

The three approaches

ApproachWhat it doesFailure mode
Reset (modern-normalize, sanitize.css)removes defaults, sets a basezeroes things you wanted; specificity is often 0,0,1+ and fights components
Normalizekeeps defaults, harmonises cross-browserwas written for a world with IE; much of it is now UA behaviour
Layered base (@layer base)sets defaults at a low priorityrequires the rest of your CSS to also use layers, or it does nothing

@layer is the answer that actually holds, because it fixes the reason resets are written with !important and 4-selector chains: a base layer loses to unlayered styles by definition, so your component code never has to out-weigh your reset.

@layer reset, base, components, utilities;

@layer reset {
  *, *::before, *::after { box-sizing: border-box; }
  * { margin: 0; }
  html { color-scheme: light dark; block-size: 100%; }
  body { min-block-size: 100dvh; -webkit-font-smoothing: antialiased; }
  img, picture, video, canvas, svg { display: block; max-inline-size: 100%; }
  input, button, textarea, select { font: inherit; color: inherit; }
  p, h1, h2, h3, h4 { overflow-wrap: break-word; }
  :where(h1, h2, h3, h4, h5, h6) { font-size: inherit; text-wrap: balance; }
}

That is a complete, current reset: about a dozen declarations. Two details are load-bearing:

  • :where() on the heading rule contributes zero specificity, so a single .title class beats it. Write the same rule as h1,h2,h3 { font-size: inherit } inside a layer and you have made the layer do work it does not need to do — the layer already handles precedence, and the :where() still helps when a consumer of your CSS is not using layers.
  • font: inherit on form controls. They do not inherit font-family otherwise, and this single line is why your inputs stopped looking like Times New Roman on an all-sans page.

What to leave alone

  • user-select: none — never put it in a reset. It breaks copying, and on mobile it breaks text selection entirely.
  • -webkit-tap-highlight-color: transparent — removes the only tactile feedback a touch link has.
  • scroll-behavior: smooth globally — it makes every in-page jump and every focus move animate, which is a motion effect you applied to every user including the ones who asked not to have one. Put it on html:root only with a prefers-reduced-motion guard.
  • all: unset as a universal reset. It is a sledgehammer that also resets direction behaviour unpredictably across engines and destroys display on elements you need to keep in flow.

Dark mode without a class toggle

Set color-scheme: light dark and drive tokens through light-dark(); the OS toggle then styles form controls, scrollbars, and native pickers in addition to your own surfaces. A reset that hard-codes background: white on body is the reason "dark mode" pages still flash a white background on load — the fix is background: Canvas (a system colour) or a token, never a literal.

The check that tells you a reset is too aggressive

Open DevTools, click a plain <button>, and look at the computed border, background, cursor, and font-family. If cursor is default rather than pointer you have reset a control into a label, and users will hit it twice before they trust it. Buttons are not divs; keeping their appearance and only changing paint is a different, smaller job.

Then add your own base layer on top: selector and specificity reference covers why :where() matters here, and patterns and gotchas covers the cascade order that layers participate in.

Developer Sponsor / Partner
Copied to clipboard!