Difference between CSS reset and normalize
CSS Reset vs Normalize.css - Reset wipes every browser default style to zero; Normalize fixes browser inconsistencies while keeping useful defaults like bold headings and list bullets.
Theory
TL;DR
- Reset = bulldoze to bare ground, build everything yourself; Normalize = fix uneven floors across different houses without demolishing them
- Reset zeroes margins, padding, list bullets, and font weights; Normalize keeps heading hierarchy and list styles intact
- Normalize v8.0.1 is 7.3KB minified; most Reset files range from 1-5KB depending on the author
- Total custom design (game UI, data dashboard) → Reset; production web app with semantic HTML → Normalize
Quick example
/* Default browser behavior: Chrome adds ~21px margin-top to h1, bold font */
/* Approach A: CSS Reset */
*, *::before, *::after {
margin: 0;
padding: 0;
box-sizing: border-box;
}
/* Result: h1 sits flush at viewport top, no bold, no margin */
/* ul renders without bullets or left indent - you write everything from here */
/* Approach B: Normalize v8.0.1 snippet */
h1 { font-size: 2em; margin: .67em 0; } /* heading hierarchy preserved */
/* Result: h1 is bold with spacing, ul keeps bullets */
/* Chrome and Firefox now render identically */Both start from the same problem: browsers disagree on defaults. They just solve it differently.
Key difference
Reset targets uniformity by setting margins, padding, borders, and list styles to zero on every element. You build from an empty canvas. Normalize targets only the parts where browsers disagree, like Chrome's 16px base font versus Firefox's 15px, leaving semantic styles (bold h1, ul bullets) intact. One zeroes everything; the other patches what is broken.
When to use
- Custom design from scratch, no semantic defaults needed (game UI, custom dashboard) → Reset
- Production web app, content site, or React/Next.js app → Normalize
- Working with Tailwind CSS → Normalize pairs naturally; Tailwind has its own Preflight for utility-first needs, but Normalize suits form styles outside that system
- Legacy browser support where predictable zero-baselines matter → Reset
Most React codebases I have seen default to Normalize for exactly this reason: the app uses semantic HTML, and nobody wants to rewrite heading bold weights just because they zeroed everything at the start.
Comparison table
| Aspect | CSS Reset | Normalize.css |
|---|---|---|
| Approach | Zeroes all defaults | Fixes inconsistencies, keeps useful defaults |
| File size | ~1-5KB (varies by author) | 7.3KB minified (v8) |
| Headings | Normal weight, no margin | Bold, 0.67em margin |
| Lists | No bullets or markers | Bullets preserved, consistent across browsers |
| Forms | Borders and margins zeroed | Consistent focus states added |
| When to use | Custom design with full control | Production sites with semantic HTML |
How browsers process this
Browsers apply user agent stylesheets (UA styles) during the CSS cascade, before your own styles parse. Reset injects a high-specificity * { margin: 0 } that overrides all UA defaults across the board. Normalize uses targeted selectors, like textarea { resize: vertical }, with !important in only about 5 places in v8, patching specific bugs in Blink, Gecko, and WebKit without broad overrides. That is why Normalize plays nicer with your own cascade.
Common mistakes
Applying Reset and Normalize together:
/* Wrong: Reset's blanket * rule makes Normalize's targeted fixes irrelevant */
* { margin: 0; padding: 0; }
h1 { margin: .67em 0; } /* Already zeroed by * - pointless */Pick one. Using both means Normalize's targeted rules fight the Reset's blanket * selector, and you get unpredictable results.
Forgetting box-sizing in a custom Reset:
/* Wrong: content-box default breaks width calculations */
div { width: 100%; padding: 10px; } /* renders 20px wider than expected */
/* Correct: always include box-sizing in any modern Reset */
*, *::before, *::after { box-sizing: border-box; }This is the most common Reset-related bug, and it still shows up in code reviews.
Using Eric Meyer's 2008 Reset unchanged: It misses modern SVG elements, form states, and a decade of CSS additions. Switch to Normalize or a maintained modern reset file.
Assuming Normalize changes nothing visually:
It fixes 200+ browser-specific bugs, including Firefox button gradients and Safari's html font-size quirk. The output looks similar, but cross-browser behavior is far more predictable. Inspect button:focus in Chrome DevTools before and after loading Normalize to see the difference.
Real-world usage
- Create React App: imports Normalize v8.0.1 by default for consistent forms and headings
- Bootstrap 5: builds its grid and component system on top of Normalize
- Next.js: defaults to Normalize via globals.css
- Tailwind CSS: uses its own Preflight internally; Normalize is recommended for form styles outside the Tailwind ecosystem
Follow-up questions
Q: Why not just write * { margin: 0; padding: 0; } yourself instead of pulling in a library?
A: That one rule misses 100+ browser-specific bugs, including Safari's html element font-size quirk. Maintained libraries handle those edge cases automatically.
Q: Does Normalize use !important?
A: Rarely. Normalize v8 uses it in about 5 rules, each targeting a known browser bug. It avoids broad !important usage to stay compatible with your cascade.
Q: What is the difference between Tailwind Preflight and Normalize?
A: Preflight is Reset-like but built for Tailwind's utility-first system and adds opinionated defaults like ring colors. Outside of Tailwind projects, Normalize is the more neutral choice.
Q: How do you inspect what Normalize is actually fixing?
A: Open Chrome DevTools, select any element, go to the Styles panel, and look for "user agent stylesheet" entries. Those are the defaults Normalize patches for consistency.
Q: In a CSS-in-JS setup like Emotion, how does Normalize interact with scoped styles?
A: Import Normalize globally. Your scoped component selectors win over its rules due to specificity. But test UA style leaks in iframes, like Stripe embedded widgets, where styles may not inherit as expected.
Examples
Reset vs Normalize on basic HTML elements
<!DOCTYPE html>
<html>
<head>
<style>
/* Scenario A: CSS Reset */
*, *::before, *::after {
margin: 0;
padding: 0;
box-sizing: border-box;
}
/* h1 sits flush at viewport top, no bold, no margin */
/* ul renders without bullets or left indent */
/* Scenario B: Normalize v8.0.1 snippet */
/* h1 { font-size: 2em; margin: .67em 0; } */
/* h1 keeps bold + spacing; ul bullets stay */
</style>
</head>
<body>
<h1>Heading</h1>
<ul>
<li>List item one</li>
<li>List item two</li>
</ul>
</body>
</html>With Reset, the h1 sits flush against the viewport top with no bold weight and no margin. The list has no bullets. With Normalize, the h1 keeps its 0.67em margin and bold weight. The list stays bulleted. Both results are consistent across Chrome and Firefox, just from different starting points.
React app with Normalize as the baseline
/* index.css - Create React App imports normalize.css automatically */
@import 'normalize.css';
/* Component styles build on top of Normalize's baseline */
.article-title {
font-size: 2.5rem;
color: #1a1a2e;
/* Normalize already set bold + base font-size on h1 - no need to redefine */
}
/* Forms work consistently without extra resets */
input[type="text"] {
border: 1px solid #ccc;
padding: 8px 12px;
/* Normalize already fixed cross-browser font-family and padding on inputs */
}Create React App uses Normalize by default because React apps rely on semantic HTML. Heading hierarchy and list styles carry meaning in content-driven UIs. Normalize lets you keep that meaning without writing a single baseline style yourself.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.