CSS Resets in 2026: modern-normalize, @layer, and What Actually Needs Resetting
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:
dialogandpopovercome with focus handling and a backdrop;all: unseton them removes the centering you thought you were resetting.- Form controls inherit
colorandbackgroundfrom the system scheme when you setcolor-scheme; resetting them to white on a dark page is the classic "why is the date picker white" bug. prefers-reduced-motionis not honoured by any reset; that has always been your problem.- Headings inside
section/articlestill get level-correct sizing, so zeroing allfont-size: inheritmeans your document outline is now invisible in the rendered page.
The three approaches
| Approach | What it does | Failure mode |
|---|---|---|
Reset (modern-normalize, sanitize.css) | removes defaults, sets a base | zeroes things you wanted; specificity is often 0,0,1+ and fights components |
| Normalize | keeps defaults, harmonises cross-browser | was written for a world with IE; much of it is now UA behaviour |
Layered base (@layer base) | sets defaults at a low priority | requires 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.titleclass beats it. Write the same rule ash1,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: inheriton form controls. They do not inheritfont-familyotherwise, 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: smoothglobally — 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 onhtml:rootonly with aprefers-reduced-motionguard.all: unsetas a universal reset. It is a sledgehammer that also resetsdirectionbehaviour unpredictably across engines and destroysdisplayon 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.