ESC

Type to search the knowledge base.

CSS Reset vs Normalize vs Layer

Reset wipes UA styles, normalize preserves useful defaults consistently, layers order both — pick a base strategy without fighting components.

intermediate3 min read
  • css
  • reset
  • normalize

Every page starts with the user-agent stylesheet: margins on body, list bullets, blue underlined links, bold headings. Resets strip those defaults aggressively. Normalize-style sheets make defaults consistent across browsers while keeping sensible behaviors. @layer decides whether your base loses to components and utilities on purpose.

Docs: MDN cascade, Reboot/Normalize history, modern alternatives like modern-normalize.

Reset (scorched earth)

@layer reset {
  *,
  *::before,
  *::after {
    box-sizing: border-box;
    margin: 0;
  }

  html {
    -webkit-text-size-adjust: 100%;
  }

  body {
    min-height: 100%;
    line-height: 1.5;
  }

  img,
  picture,
  video,
  canvas,
  svg {
    display: block;
    max-width: 100%;
  }

  input,
  button,
  textarea,
  select {
    font: inherit;
  }

  p,
  h1,
  h2,
  h3,
  h4,
  h5,
  h6 {
    overflow-wrap: break-word;
  }

  list-style: none; /* apply carefully — see a11y note */
}

Classic resets zero margins on everything and remove list styles. That forces you to re-add spacing and list semantics styling — good for design systems with intentional type ramps; noisy for content sites.

A11y note: if you remove list markers with list-style: none, some VoiceOver versions historically stopped announcing lists unless you compensate (e.g. role="list" or other mitigations). Prefer intentional list styling over blind global nukes.

Normalize / opinionated base

Normalize aims for predictable UA behavior:

  • Correct display on HTML5 elements
  • Consistent heading sizes / form font inheritance
  • Fix legacy browser bugs
@layer reset {
  /* excerpt of a modern base, not a full library copy */
  body {
    margin: 0;
  }

  b,
  strong {
    font-weight: bolder;
  }

  small {
    font-size: 80%;
  }

  button,
  input,
  select,
  textarea {
    font-family: inherit;
    font-size: 100%;
  }
}

You’re not inventing design — you’re removing cross-browser surprises.

Layer them so utilities win

@layer reset, base, components, utilities;

@layer reset {
  /* modern-normalize or your reset */
}

@layer base {
  body {
    font-family: var(--font-sans);
    color: var(--text);
    background: var(--surface);
  }

  a {
    color: var(--link);
  }

  :focus-visible {
    outline: 2px solid var(--brand);
    outline-offset: 2px;
  }
}

Without layers, a reset shipped late in the bundle can clobber component CSS. With layers, order is explicit.

Which to pick

Context Strategy
Design system / app shell Minimal reset + tokens + components in layers
Content/marketing site Lighter normalize; keep heading defaults, tune type
Embedding third-party widgets Don’t global-reset inside their subtree
Tailwind-like setup Preflight (opinionated reset) + utilities layer

Interview out-loud

“A reset removes most UA margins and defaults so the design system owns spacing. Normalize makes browsers behave consistently while keeping useful defaults. I put base styles in a low @layer under components and utilities so overrides don’t need specificity hacks. I’m careful with list-style resets for accessibility.”

Footguns

  1. Double-applying reset and a framework preflight.
  2. Resetting button styles then forgetting focus rings.
  3. * { margin: 0 } without a spacing system — inconsistent ad-hoc margins return.
  4. Unlayered reset after layered components.
  5. Copy-pasting a 2012 reset with obsolete IE fixes and aggressive outline: none.

Minimal modern reset many apps need

Beyond box-sizing and margin zero on body:

@layer reset {
  img, svg, video { display: block; max-width: 100%; }
  button, input, select, textarea { font: inherit; color: inherit; }
  button { cursor: pointer; }
  a { color: inherit; }
  p, h1, h2, h3, h4, h5, h6 { overflow-wrap: anywhere; }
  :focus:not(:focus-visible) { outline: none; }
  :focus-visible { outline: 2px solid var(--brand, #0b6bcb); outline-offset: 2px; }
}

That last focus pair prevents mouse-focus rings without killing keyboard visibility — better than a blunt global outline: none.

Further reading

Related guides