This guide is written for the people who own normalize.css at the architectural level: front-end architects deciding whether and how to adopt it, build engineers wiring it into a pipeline, design-system maintainers who extend or fork it, and anyone responsible for the long-term maintenance and security posture of a project that depends on it.
It documents the project as it actually exists in source (v8.0.1, commit
fc091cc): a single, dependency-free, statically authored CSS file. Where most
admin guides cover servers, configuration, and runtime operations, this one covers
the cascade architecture, integration pipeline, customization strategy, the
complete normalization catalog, and the maintenance reality of a library whose
entire deployable surface is one stylesheet.
1. Executive Summary
normalize.css is a CSS-only baseline library. It contains no executable code,
no configuration, no dependencies, and no build artifacts beyond the hand-authored
normalize.css file itself. Its job is to make HTML elements render consistently
across browsers by correcting documented user-agent-stylesheet inconsistencies,
while deliberately preserving useful browser defaults (unlike a blanket CSS
reset).
| Attribute | Value |
|---|---|
| Type | Static CSS stylesheet (baseline / "normalizer") |
| Version | 8.0.1 |
| Source commit | fc091cc (2018-11-04) |
| License | MIT |
| Runtime dependencies | None |
| Build dependencies | None required to consume; CI runs on Node.js |
| JavaScript | None |
| Configuration surface | None (customization is done by overriding in your own CSS) |
| Deployable surface | One file, ~350 lines |
| Browser targets | Chrome, Edge, Firefox ESR+, IE 10+, Safari 8+, Opera |
| Maintenance status | Frozen — last release November 2018 |
Architectural implication for adopters: the integration risk is essentially zero (no code executes, nothing to misconfigure, nothing to patch for CVEs in the file itself), but the file is frozen in time. Treat it as a stable dependency you pin and rarely update, and own your own customization layer on top of it.
Important
The single most important operational fact about this dependency is that it has not had a release since 8.0.1 in November 2018. It is safe to use precisely because it targets long-standing, stable browser behaviors — but you should plan on it never changing. See Section 9.
2. Architecture
There is no runtime "system" to diagram in the usual sense — the architecture is the structure of the stylesheet itself and how it sits in the CSS cascade.
2.1 Position in the cascade
Browser User-Agent Stylesheet (each browser's own defaults — inconsistent)
│
▼
normalize.css (corrects the inconsistencies; keeps useful defaults)
│
▼
Your base styles / framework (typography, resets you do want, design tokens)
│
▼
Your component & page styles (highest authority — always wins)
normalize.css occupies the base layer directly above the browser's own user-agent stylesheet. It intentionally does the minimum: it does not impose a design, it only removes cross-browser disagreement so the layers above it start from a predictable footing.
2.2 File organization
The file is divided into eight comment-bannered sections, ordered roughly from document-level down to individual control quirks:
| # | Section banner | Concern |
|---|---|---|
| 1 | Document | Root html element (line height, iOS text-size-adjust) |
| 2 | Sections | body, main, h1 |
| 3 | Grouping content | hr, pre |
| 4 | Text-level semantics | inline elements (a, abbr, b/strong, code/kbd/samp, small, sub/sup) |
| 5 | Embedded content | img |
| 6 | Forms | all form controls (the largest section) |
| 7 | Interactive | details, summary |
| 8 | Misc | template, [hidden] |
2.3 Authoring conventions (the "design rules" of the file)
The project enforces a deliberate authoring style, documented in CONTRIBUTING.md.
These conventions are the architecture of the file — they keep it auditable:
- Comments are short and to the point.
- A comment without a number references the entire rule; numbered comments
(
/* 1 */) map to the numbered property they annotate. - Comments describe the selector when the selector alone does not make the normalization obvious.
- Comments begin with "Correct the…" when they deal with less-obvious side effects.
- Rules are sorted by cascade, then specificity, then alphabetic order.
- Selectors within a rule are sorted by specificity, then alphabetic order.
- Browser-version shorthand in comments:
browser= all versions,browser v-= up to and including a version,browser v+= that version and later,browser v-v= an inclusive range.
Tip
When you fork or extend the file, follow these conventions exactly. The whole value of normalize.css is that each rule is self-documenting — every property explains which browser bug it fixes. A fork that drops the comments loses the primary reason to use normalize.css over an ad-hoc reset.
3. Requirements and Compatibility
3.1 Consumer requirements
There are effectively none. To use normalize.css you need only the ability to serve a CSS file or import one. There is no Node.js, no compiler, and no runtime requirement imposed on consumers.
3.2 Supported browsers (rendering targets)
| Browser | Supported | Notes |
|---|---|---|
| Chrome | Current | Full target |
| Edge | Current | Full target |
| Firefox | ESR+ | ESR and later |
| Internet Explorer | 10+ | Several rules exist only to fix IE 10/11 |
| Safari | 8+ | Includes macOS and iOS WebKit quirks |
| Opera | Current | Full target |
Support for Android 4, IE ≤ 9, and Safari ≤ 7 was dropped in v8.0.0. The codebase therefore assumes those engines are out of scope, and contains no workarounds for them.
3.3 Development/CI requirements
The repository's continuous integration runs on Node.js (.travis.yml specifies
language: node_js, node_js: stable). This is only relevant if you are
contributing to or forking the project — it is not a requirement for consuming the
stylesheet.
Note
The
package-lock.jsonin the repo declares only the package's own identity (normalize.css@8.0.1, lockfile v1) with no resolved dependency tree — consistent with a package that has zero runtime and (effectively) zero build dependencies.
4. Integration and Build Pipeline
Although normalize.css needs no build of its own, in a real project it flows through your asset pipeline. The architectural rule is invariant across every toolchain: normalize.css is emitted first, your styles after.
4.1 Delivery channels
| Channel | Mechanism | Best for |
|---|---|---|
| npm / Yarn | node_modules/normalize.css/normalize.css |
Bundled apps |
| CDN (jsDelivr, unpkg, cdnjs) | <link> to a versioned URL |
Static sites, prototypes |
| Vendored file | Commit the raw file into your repo | Air-gapped / fully-controlled builds |
| Inlined | Paste into a base stylesheet or <style> |
Single-file demos, critical-CSS inlining |
4.2 Bundler integration
In webpack, Vite, Rollup, Parcel, etc., import normalize.css before any of your own style imports in the entry module, because emitted CSS order follows import order:
// entry.js
import 'normalize.css'; // base layer — first
import './styles/tokens.css';
import './styles/app.css';
For Sass/PostCSS pipelines, forward it at the top of the root stylesheet:
@import 'normalize.css/normalize.css'; // base layer
@import 'design-tokens';
@import 'components';
4.3 Minification and optimization
The project ships no minified build (a request was explicitly declined, issue #911). Produce one in your own pipeline:
# example: using a CSS minifier of your choice
csso normalize.css -o normalize.min.css
Because the file is tiny (~350 lines, a few kilobytes), it is also a good candidate for inlining into critical CSS rather than loading as a separate request.
4.4 Caching and CDN strategy
When served from a CDN, pin the exact version in the URL (normalize.css@8.0.1)
and cache aggressively — the file will not change. For self-hosted delivery,
fingerprint the filename (normalize.[hash].css) so you can set a far-future
Cache-Control while still being able to bust the cache if you ever swap versions.
Tip
If you serve normalize.css from a third-party CDN, add Subresource Integrity (an
integrityhash on the<link>) so a compromised CDN cannot silently alter your baseline styles. See Section 8.2.
5. Customization Strategy (the "Configuration" Reference)
normalize.css has no configuration: no variables, no flags, no options object,
no Sass !default layer. This is a deliberate design choice — the file is a flat
list of corrections, not a configurable framework. "Configuring" normalize.css
means layering your own CSS on top of it.
5.1 The override layer
The supported customization model is a separate stylesheet that loads after normalize.css and overrides exactly the rules you want to change:
/* your-base.css — loaded AFTER normalize.css */
/* re-introduce a default body margin (normalize sets it to 0) */
body { margin: 8px; }
/* opt into your own monospace stack instead of the duplicated-monospace hack */
pre, code, kbd, samp { font-family: "JetBrains Mono", monospace; }
/* brand link color (normalize only makes the IE10 active background transparent) */
a { color: var(--brand); }
Because these rules come later in the cascade and match the same specificity, they
win cleanly without !important.
5.2 The fork-and-prune model
For teams that drop Internet Explorer entirely, the alternative is to fork the file and delete rules whose comments name only unsupported browsers. The sections most affected are:
| Candidate rule | Reason it exists | Safe to drop if… |
|---|---|---|
main { display: block } |
IE renders main as inline |
You never support IE 10/11 |
a { background-color: transparent } |
Gray active-link bg in IE 10 | No IE 10 |
img { border-style: none } |
Image border in links, IE 10 | No IE 10 |
textarea { overflow: auto } |
Default scrollbar in IE 10+ | No IE 10/11 |
[type="checkbox"]/[type="radio"] { box-sizing } |
IE 10 box sizing | No IE 10 |
template { display: none } |
IE 10+ | No IE |
Caution
Most rules are not browser-specific — the form
font-family: inherit,sub/supline-height, theabbr[title]underline, and themonospace, monospacesizing fix all apply to current browsers. Pruning those re-introduces live inconsistencies. Only remove rules whose comments name exclusively browsers you have formally dropped, and keep the comments on what remains.
5.3 The one opinion to know about
body { margin: 0 } is the only purely opinionated layout rule in the file. Every
other declaration corrects a genuine inconsistency or bug. Architecturally, this
means normalize.css is almost purely corrective — when you override a normalize
rule other than the body margin, you are usually choosing to re-accept a browser
quirk, so do it intentionally.
6. The Normalization Catalog (Internals)
This is the exhaustive, rule-by-rule reference — the equivalent of an API
reference for a code library. Every declaration in normalize.css v8.0.1 is
listed with its selector, the exact value it sets, and the browser behavior it
corrects (taken verbatim from the file's own comments).
6.1 Document
| Selector | Declaration | Correction |
|---|---|---|
html |
line-height: 1.15 |
Correct the line height in all browsers |
html |
-webkit-text-size-adjust: 100% |
Prevent font-size adjustments after orientation changes in iOS |
6.2 Sections
| Selector | Declaration | Correction |
|---|---|---|
body |
margin: 0 |
Remove the default margin in all browsers (opinionated) |
main |
display: block |
Render main consistently in IE |
h1 |
font-size: 2em; margin: 0.67em 0 |
Correct size/margin of h1 within section/article in Chrome, Firefox, Safari |
6.3 Grouping Content
| Selector | Declaration | Correction |
|---|---|---|
hr |
box-sizing: content-box; height: 0 |
Add correct box sizing in Firefox |
hr |
overflow: visible |
Show the overflow in Edge and IE |
pre |
font-family: monospace, monospace |
Correct inheritance and scaling of font-size in all browsers (duplication intentional) |
pre |
font-size: 1em |
Correct the odd em font sizing in all browsers |
6.4 Text-level Semantics
| Selector | Declaration | Correction |
|---|---|---|
a |
background-color: transparent |
Remove gray active-link background in IE 10 |
abbr[title] |
border-bottom: none |
Remove bottom border in Chrome 57- |
abbr[title] |
text-decoration: underline dotted |
Add correct text decoration in Chrome, Edge, IE, Opera, Safari (with underline fallback) |
b, strong |
font-weight: bolder |
Add correct font weight in Chrome, Edge, Safari |
code, kbd, samp |
font-family: monospace, monospace; font-size: 1em |
Same monospace inheritance/scaling fix as pre |
small |
font-size: 80% |
Add correct font size in all browsers |
sub, sup |
font-size: 75%; line-height: 0; position: relative; vertical-align: baseline |
Prevent sub/sup from affecting line height in all browsers |
sub |
bottom: -0.25em |
Subscript positioning |
sup |
top: -0.5em |
Superscript positioning |
6.5 Embedded Content
| Selector | Declaration | Correction |
|---|---|---|
img |
border-style: none |
Remove the border on images inside links in IE 10 |
6.6 Forms
| Selector | Declaration | Correction |
|---|---|---|
button, input, optgroup, select, textarea |
font-family: inherit; font-size: 100%; line-height: 1.15; margin: 0 |
Change font styles in all browsers; remove margin in Firefox & Safari |
button, input |
overflow: visible |
Show overflow in Edge & IE |
button, select |
text-transform: none |
Remove inherited text-transform in Edge, Firefox, IE |
button, [type="button"], [type="reset"], [type="submit"] |
-webkit-appearance: button |
Correct inability to style clickable types in iOS & Safari |
button::-moz-focus-inner (and the three [type] variants) |
border-style: none; padding: 0 |
Remove inner border and padding in Firefox |
button:-moz-focusring (and the three [type] variants) |
outline: 1px dotted ButtonText |
Restore focus styles unset by the previous rule |
fieldset |
padding: 0.35em 0.75em 0.625em |
Correct padding in Firefox |
legend |
box-sizing: border-box; color: inherit; display: table; max-width: 100%; padding: 0; white-space: normal |
Correct text wrapping (Edge/IE), color inheritance (IE), remove padding |
progress |
vertical-align: baseline |
Add correct vertical alignment in Chrome, Firefox, Opera |
textarea |
overflow: auto |
Remove default vertical scrollbar in IE 10+ |
[type="checkbox"], [type="radio"] |
box-sizing: border-box; padding: 0 |
Add correct box sizing and remove padding in IE 10 |
[type="number"]::-webkit-inner-spin-button, …outer-spin-button |
height: auto |
Correct cursor style of increment/decrement buttons in Chrome |
[type="search"] |
-webkit-appearance: textfield; outline-offset: -2px |
Correct odd appearance (Chrome/Safari) and outline style (Safari) |
[type="search"]::-webkit-search-decoration |
-webkit-appearance: none |
Remove inner padding in Chrome/Safari on macOS |
::-webkit-file-upload-button |
-webkit-appearance: button; font: inherit |
Correct inability to style clickable types in iOS/Safari; inherit font in Safari |
6.7 Interactive
| Selector | Declaration | Correction |
|---|---|---|
details |
display: block |
Add correct display in Edge, IE 10+, Firefox |
summary |
display: list-item |
Add correct display in all browsers |
6.8 Misc
| Selector | Declaration | Correction |
|---|---|---|
template |
display: none |
Add correct display in IE 10+ |
[hidden] |
display: none |
Add correct display in IE 10 |
6.9 Esoteric rules worth understanding
A handful of rules look strange out of context. The project documents each:
monospace, monospace— duplicating the keyword fixes inheritance and scaling offont-sizefor preformatted/monospace text.sub/supwithline-height: 0+position: relative— normally superscripts/subscripts inflate a line's box height; this neutralizes that in all browsers while still nudging the glyphs up/down.selectstyling limits — Chrome/Safari on macOS allow only limitedselectstyling unless aborderis set, andoptgroupfont-weight cannot be safely changed there.[type="checkbox"]/[type="radio"]— Firefox does not respectbox-sizing,padding, orwidth; styling the native box is discouraged.[type="number"]cursor — certain font sizes change the decrement button's cursor fromdefaulttotext.[type="search"]— search inputs are not fully stylable;-webkit-appearance: textfieldrestores control without losing native search affordances.
7. Testing
The project ships a single visual test harness, test.html, rather than an
automated assertion suite. It is a "describe/it"-style document (using the
SUIT-test CSS counter convention) that renders every normalized element with a
short expectation for each — for example:
htmlshould have a line height of 1.15bodyshould have no margin (opinionated)- sectioning elements (
article,aside,details,figure,footer,header,main,menu,nav,section,summary) should render as block sub/supshould not affect a line's visual line-height- form controls should inherit
font-sizefrom their ancestor and have no margins [type="checkbox"]/[type="radio"]should have aborder-boxmodel and no padding
The test page deliberately sets body { background: red } so that a correctly
applied body { margin: 0 } leaves no red visible around the content — a quick
visual pass/fail.
Note
Because the test is visual, "running tests" means opening
test.htmlin each supported browser and confirming the expectations hold. There is no headless test runner in the repository. If you fork and modify rules, updatetest.htmlto cover your change (the contribution guidelines require adding a test "if appropriate").
8. Security
normalize.css contains no executable code, so its own attack surface is minimal. The relevant security concerns are about delivery and integration, not the file's contents.
8.1 Content Security Policy
normalize.css is a plain stylesheet and imposes no CSP requirements itself. Under a
strict CSP you must allow its source in style-src. If you inline it or add it via
a <style>/<link> that your CSP would otherwise block, attach the appropriate
nonce (or hash) per your policy (this is the substance of issue #908 — there is
nothing normalize-specific to configure).
Content-Security-Policy: style-src 'self' https://cdn.jsdelivr.net;
8.2 Supply chain
The realistic risk is a compromised or tampered delivery path, not the file itself:
-
Pin the version in npm and in CDN URLs so the baseline cannot shift under you.
-
Add Subresource Integrity when loading from a third-party CDN:
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/normalize.css@8.0.1/normalize.css" integrity="sha384-…" crossorigin="anonymous"> -
Vendor the file (commit it into your repo) for fully air-gapped or high-assurance builds, eliminating the network dependency entirely.
8.3 What normalize.css does not do
It does not sanitize input, does not render user content, does not execute scripts, and does not read or write storage. CSS-level injection concerns (e.g. untrusted CSS in user content) are out of scope for this file and must be handled wherever your application accepts styles.
Tip
Because the file is frozen at 8.0.1, you can compute its SRI hash once and treat any future hash mismatch on that exact URL as a tampering signal.
9. Maintenance and Known Issues
9.1 Maintenance status
This is the defining operational fact about the dependency: the most recent release is 8.0.1, dated November 4, 2018. The community has openly asked whether the repository is still maintained (issue #880, open). No releases have followed.
What this means in practice:
- The file is stable and safe to depend on because it targets long-standing browser behaviors that have not changed.
- You should not expect bug fixes, new normalizations, or support for newer browser quirks from upstream.
- For new projects you should make a deliberate decision: adopt v8.0.1 as a frozen baseline (and own your override layer), or maintain your own pruned fork tuned to the browsers you actually support.
Warning
Do not wire your build to a floating "latest" version expecting ongoing updates — there are none. Pin
8.0.1explicitly so your baseline is reproducible.
9.2 Open issues affecting users
The following are drawn from the project's issue tracker and represent the live rough edges adopters encounter:
| Issue | Element | Summary | Practical impact |
|---|---|---|---|
| #886 | <button> |
iOS sets button text color to blue via the user-agent stylesheet | Set color explicitly on buttons |
| #838 | buttons/inputs | 2px default border-radius in Microsoft Edge | Zero border-radius if you need square corners |
| #910 | inputs | Line-height behavior on inputs | Set an explicit line-height where it matters |
| #904 | progress, [type="range"] |
Odd vertical alignment | Override vertical-align per layout |
| #926 | [hidden] |
The [hidden] { display: none } rule's interaction with the HTML standard |
Be aware when toggling hidden alongside other display rules |
| #933 | unicode | iOS confuses some unicode characters with emoji | Platform behavior; outside normalize's scope |
| #819 | abbr[title] |
Request to extend Safari support | Not merged; current rule still applies |
| #908 | CSP | How to add a nonce |
Configure CSP at the page/server level |
| #882 | docs | A broken documentation link on the project site | Cosmetic; does not affect the stylesheet |
9.3 Potential improvements (for forks)
If you maintain a fork, the practical modernization opportunities are:
- Drop the IE-only rules (
main { display: block }, the IE-10 background/border fixes, the IE-10/11 box-sizing rules) once IE is formally out of support, slimming the file. - Publish a minified artifact in your fork (upstream declined to, issue #911).
- Add a
prefers-color-scheme/ color-mode baseline if your design system needs one — normalize.css v8 predates widespread dark-mode adoption and does nothing here. - Add an automated visual-regression test to replace the manual
test.htmlpass (the upstream test is purely visual).
Note
These are fork improvements, not upstream roadmap items. Given the frozen maintenance status, do not expect any of them to land in
necolas/normalize.css.
10. Code Quality and Conventions
The file's quality model is encoded in CONTRIBUTING.md and is unusually strict
for a stylesheet — appropriate, because the file doubles as documentation.
10.1 Comment discipline
- Comments are short and to the point.
- Numberless comments describe the whole rule; numbered comments map to numbered properties.
- Comments describe the selector when it is not self-explanatory.
- Comments dealing with non-obvious side effects begin with "Correct the…".
- The browser-version shorthand (
v-,v+,v-v) keeps affected-version notes terse and unambiguous.
10.2 Ordering rules
- Rules are sorted by cascade, then specificity, then alphabetically.
- Selectors within a rule are sorted by specificity, then alphabetically.
10.3 Semantic versioning strategy
normalize.css uses a stricter-than-usual interpretation of SemVer:
| Change type | Version bump |
|---|---|
| Any change to a CSS rule (it alters rendering) | Major |
| Change with no impact on rendering (comments, metadata) | Patch |
| (There are effectively no "minor" releases) | — |
The rationale: no change to a CSS rule can add functionality in a backwards-compatible way, so every rule change is treated as breaking. This is why the changelog jumps 4.x → 5.x → 6.x → 7.x → 8.x on what look like small edits, and why pinning an exact version is mandatory for reproducible styling.
11. Maintenance Operations (for Maintainers/Forkers)
If you own a fork or have commit access to a downstream copy, the release process
from CONTRIBUTING.md is:
- Record functional changes in the changelog.
- Bump the version in a dedicated commit — the version must be updated in the
changelog (with date),
package.json, and the header comment ofnormalize.cssitself. - Use a
v0.0.0-format commit message for the version bump. - Create an annotated tag:
git tag -m "v0.0.0" 0.0.0. - Push with tags:
git push --tags origin master. - Publish the site from the
gh-pagesbranch per the README instructions.
For accepting patches, the project's stated policy is: verify the patch is within
the project's scope and philosophy, ensure it carries any necessary test and a
proper commit message, test locally, and apply patches to master locally (via
git am or a branch checkout) rather than using GitHub's merge button.
Tip
The three-places-must-match version rule (changelog +
package.json+ file header comment) is the most common release mistake. Automate it or check all three before tagging — a version that disagrees with the header comment will confuse every downstream consumer reading the file directly.
12. Quick Reference
12.1 Facts
| Item | Value |
|---|---|
| Package | normalize.css @ 8.0.1 |
| Commit | fc091cc (2018-11-04) |
| License | MIT |
| Files published | normalize.css, LICENSE.md |
| Entry points | main and style → normalize.css |
| Dependencies | None |
| JavaScript | None |
| Config | None (override in your own CSS) |
| Test | test.html (manual, visual) |
| CI | Travis, Node.js stable |
| Homepage | https://necolas.github.io/normalize.css |
12.2 Integration checklist
- [ ] Pinned exact version
8.0.1(npm and/or CDN URL) - [ ] normalize.css imported/linked before all of your own styles
- [ ] Added Subresource Integrity if loading from a third-party CDN
- [ ] Decided on customization model (override layer vs. pruned fork)
- [ ] Re-added
bodymargin if your design depends on it - [ ] Documented the frozen maintenance status in your dependency inventory
- [ ] (If forking) updated
test.htmland the three version locations on any change
12.3 The eight sections
| # | Section | Element count |
|---|---|---|
| 1 | Document | 1 (html) |
| 2 | Sections | 3 |
| 3 | Grouping content | 2 |
| 4 | Text-level semantics | 10 selectors |
| 5 | Embedded content | 1 |
| 6 | Forms | ~14 selectors (largest section) |
| 7 | Interactive | 2 |
| 8 | Misc | 2 |