albert.walickihire me
← all solutions
css

What's the difference between reset.css and normalize.css?

A reset removes browser default styles, while normalize.css keeps them and fixes inconsistencies. What each does, and the small modern reset to use today.

A reset removes the browser's default styles so every element starts from zero, and you style everything yourself. normalize.css keeps the useful defaults (headings are still big, lists still have bullets) and only fixes the places where browsers disagree. Today most projects use neither of them in full, but a short "modern reset" like this one:

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

  * {
    margin: 0;
  }

  body {
    line-height: 1.5;
  }

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

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

  h1,
  h2,
  h3 {
    text-wrap: balance;
  }

  p {
    overflow-wrap: break-word;
  }
}

reset.css

Eric Meyer's reset is the classic one. It sets margin, padding and border to zero and font: inherit on almost every element, removes list bullets and quotation marks, and collapses table borders.

After it, an <h1> looks like body text, <strong> isn't bold and <ul> has no bullets. That makes styling predictable, but you have to restyle everything, including text from a CMS or Markdown, where you actually want those defaults back.

normalize.css

normalize.css takes the opposite approach. It keeps the defaults and fixes inconsistencies, such as <sub> and <sup> changing the line height, or form controls not using the page's font.

It was very useful when browsers differed a lot. It hasn't had a new release in years, and browsers are far more consistent now, so many of its fixes target browsers you no longer support. modern-normalize is a maintained variant for current browsers, and Tailwind's Preflight is built on top of it.

What a modern reset does

It keeps the useful defaults like normalize (heading sizes, bold <strong>, list bullets) and removes the few defaults that get in the way almost every time:

  • box-sizing: border-box includes padding and border in width and height.
  • margin: 0 removes default margins from body, headings and paragraphs, so you add spacing where you want it, often with gap.
  • display: block on images removes the extra space below images, and max-width: 100% keeps them inside their container.
  • font: inherit makes form controls use the page's font instead of the browser's default.
  • text-wrap: balance evens out line lengths in short headings.

You don't have to write your own. Josh Comeau's custom CSS reset and Andy Bell's more modern CSS reset explain every line. Andy's version removes bullets only from lists with role="list", for the Safari reason described in how to remove bullets from a list.

Keep the reset from winning specificity fights

A reset should be the weakest CSS on the page, so it never overrides your own styles. There are two ways to make sure.

Put it in a cascade layer, as in the first snippet. Styles in a layer lose to styles outside any layer, whatever their specificity. If you use several layers, declare their order once at the top of your CSS:

@layer reset, base, components;

Or wrap selectors in :where(), which has zero specificity:

/* Specificity 0,1,1: beats .menu { padding: 1rem } */
ul[role='list'] {
  padding: 0;
}

/* Specificity 0,0,0: any class wins */
:where(ul[role='list']) {
  padding: 0;
}

One thing to watch with layers: third-party CSS that isn't in a layer beats everything in your layers, too.

My recommendation: a short modern reset in @layer reset. If you use Tailwind, Preflight already does this job, so don't add another reset on top.

more solutions
work with me

Got something that needs building?

Frontend builds, full-stack features in Django, design-system work. Available for work.

See my workGet in touch