(Posts)

Blanksheet 2.0: What a CSS Reset Should Be

Updated [ CSS , Reset ]
show, don’t tell https://github.com/P233/blanksheet

A reset earns its place by what it leaves behind. Blanksheet 2.0 leaves a blank sheet: the same rules in every browser, and nothing missing that the page needs to stay readable and usable. It clears what you would design yourself and keeps what the page cannot do without: a heading loses its size but keeps its weight, a text field loses its chrome but keeps an edge, a list loses its bullets but stays a list.

What you get

On a page with no other CSS, as the preview shows in your browser:

  • Gone: margins, padding, list markers, the heading scale and the native chrome of text fields, buttons and selects. Controls take the page’s font and color; native widgets such as date inputs keep their own text color.
  • The same everywhere: every box uses border-box, de-chromed controls get a 1px frame in the text color, code matches the text size, tables collapse, and disabled text fields, buttons and selects use the system GrayText color.
  • Still there: link color and underline, strong and em, bold headings, list semantics, native focus rings, checkboxes and radios, the select arrow, and the reader’s font size and system colors.

Why it should look like this

Blank, because the rest is yours. Everything cleared is something a design replaces anyway: spacing, chrome, a heading scale. The reset brings no theme to undo: no body font stack, base line height, color palette or radius of its own. It sits in @layer reset, where any unlayered rule overrides it.

The same everywhere, because that leaves less to check in each browser. Left alone, engines disagree on unstyled controls: Firefox draws textareas and selects with 2px inset borders where Chromium and WebKit draw 1px solid ones, and each engine pads them differently. Blanksheet replaces those defaults with one shared policy, documents the exceptions it keeps, treats an undocumented difference as a bug, and runs its tests in all three engines. Fonts, system colors and native controls still differ; check the page in each browser anyway.

Not broken, because a removed default is easy to forget. Authors restore the details they notice; the rest slips through: Safari’s list announcement, a keyboard focus ring, a disabled field’s gray. Strip those, and the gaps can reach the finished page with no error to point them out. Blanksheet keeps or normalizes what would break silently: an element nobody styles looks plain, not invisible.

Written down, because a starting point is only useful if you can rely on it. The source says not only what each rule does but what a kept default costs: the select keeps its arrow, and macOS Safari ignores the select’s padding until appearance: none. You can argue with a specific line, and an agent can read the contract instead of guessing.

How the principles were made

1.0 named reasons; 2.0 names results. Blanksheet 1.0 had five principles, each a reason to touch a default: eliminate implicit layout, preserve text semantics, skip what developers always override, apply universal defaults, normalize browser differences. Testing showed the gaps. Some defaults fit two reasons and some none. “Skip” left the unstyled page different in every engine. And no principle named accessibility, which is where 1.0 broke things: list semantics in Safari, part of Firefox’s focus ring, and the disabled state of form controls.

Blanksheet 2.0 instead asks one question of every default in scope: where does it end up? There are three answers, and each default gets exactly one:

  • [B] Blank: zeroed or inherited.
  • [N] Normalize: one shared policy, where a blank is unsafe or engines disagree.
  • [S] Survivor: kept on purpose, with the reason written down.

The five 2.0 principles follow from these answers. Start blank is [B]. Start the same everywhere is [N]. Survivors split by why they stay: Keep the meaning, Keep it usable and Respect the reader. The last two are what 1.0 lacked. One rule covers all five: every value the reset sets must follow the text or the system, or be a structural minimum like 1px, never an aesthetic constant.

Every default goes through the same order: blank it if that is safe; if a blank breaks something or engines disagree, normalize it; if both would lose meaning, function or the reader’s choice, keep it and document the cost.

  • Lists: list-style: none makes Safari drop list semantics. The marker becomes an empty string instead: list-style-type: "".
  • Control chrome: a blank button still shows its label; an empty text field vanishes. Blanked, then normalized to a 1px frame.
  • Placeholders: a fixed dimming opacity can push a passing color pair below 4.5:1; at Firefox’s 54%, #333 text on white drops to about 3:1. Normalized to the text color; dimming is left to the author, who knows the colors.
  • The select arrow: the picker cue. Survivor, with its costs documented: macOS Safari ignores padding until appearance: none, and iOS Safari before 26 drops the arrow once a background is set.
  • Checkboxes: forced-colors modes cannot restore a removed appearance. Survivor.
  • Disabled controls: inheriting color deletes the browser’s disabled gray, and the opacity dimming left behind differs by engine. De-chromed controls normalize to GrayText; native widgets such as date inputs keep their own disabled look.

Automated tests check these policies in current Chromium, Firefox and WebKit. Manual checks on the oldest supported browsers caught differences that current engines no longer show, such as gray table borders in Chrome 99 and Safari 15.4.

How it differs

Resets differ less in how carefully they decide than in what they leave the author to restore.

Resets that clear most defaults, such as Eric Meyer’s reset, destyle.css, the-new-css-reset and Tailwind’s Preflight, give the cleanest start and leave the author to add back what a page needs: emphasis, link styling, control boundaries or list semantics, depending on the reset. Preflight documents these trade-offs and suggests base styles to restore them.

Resets that keep the browser’s defaults, such as normalize.css, modern-normalize and sanitize.css, fix inconsistencies and set a base line height, leaving the author to undo the browser’s chrome and spacing. The last two also add developer-preferred defaults, such as a tab size.

Blanksheet clears what a design replaces, keeps what an unstyled page needs, sets no aesthetic values of its own, and documents each choice with its cost. The audit in the repository compares these approaches in Chromium, Firefox and WebKit.

Try it

npm install blanksheet@2

The preview puts every element on one page, with a switch to turn Blanksheet off. The README covers usage and upgrading, and the source is the specification. If you would draw a line elsewhere, I’d like to hear about it.