GODRICH
#css-animation#button#loader#transition#scroll#dashboard#ui#design-trends

CSS Reset: 9 Patterns From Basics to Safe-Area

Nine css reset patterns: one recalculates padding for the safe area, one shrinks transition duration near zero when motion reduces, one turns sharp corners

Auto-plays · click a tile to jump to its section · all nine in one zip

None of the nine patterns below detect a browser by name. Each one either swaps which unconditional rule wins once a class or pseudo-class flips, recalculates a measured value the moment a constant or an input changes, or asks a direct feature-support question before choosing a path. Every demo starts on its own once the page settles and hands control back on the first pointer, keyboard, or input event, and every one of the nine keeps working with prefers-reduced-motion: reduce set.

Demo

Drag, focus, type, and toggle each panel below — the measurement line under each one updates live, so a change is never just a guess.

Why this order

Grouping the nine this way is the fastest way to see what a plain css reset actually costs to write: sometimes it's a class name, sometimes a number, and sometimes a single supports() question asked once.

Pattern Mechanism Moved frames (of 23) Cumulative area Avg. intensity
01 Browser default reset rule-swap 12 19.15% 104.5
02 Box model comparison recalculation 22 2.36% 122.3
03 Heading line-wrap balance feature-check 23 3.253% 139.6
04 Media block baseline recalculation 18 11.703% 137.6
05 Form element font inheritance rule-swap 22 13.463% 95.8
06 Keyboard focus ring rule-swap 15 6.39% 94.2
07 Notch safe-area padding recalculation 23 1.788% 110.3
08 Reduced-motion response recalculation 13 5.87% 122.2
09 Rounded corner comparison feature-check 9 1.044% 118.6

Cluster A — a class or pseudo-class decides which rule wins

Three patterns (01, 05, 06) never touch a number. Flipping RESET_SCOPE, toggling a .inherited class, or simply reaching a button by keyboard instead of a mouse decides which of two plain rules applies — the cascade does the rest. Pattern 01 covers the widest cumulative area of the set at 19.15%, because clearing spacing and list markers changes almost the whole demo panel at once.

Cluster B — a named constant decides what gets recalculated

Four patterns (02, 04, 07, 08) read a live input — a slider, a drag, a switch — and recompute a measured value against a named constant every time it changes. None of the four depend on detecting an environment; they depend on BORDER_PX, MAX_FRAME_PX, SAFE_AREA_SCALE, and REDUCED_MOTION_SCALE.

Cluster C — a supports() check picks the branch, once

Two patterns (03, 09) ask CSS.supports() a direct yes/no question and branch on the answer instead of comparing rendered rectangles over time. Pattern 03 moved in the most frames of any pattern in the set (23 of 23, tied with 07), because every rebalanced line redraws the bar chart underneath it.

Per-item code

Each fence below is a continuous slice of the demo's actual css reset code, comments translated into English, identifiers left exactly as they render.

01Browser default reset — a starting point that unifies inconsistent spacing and list markers

The default spacing and list markers that vary by browser are all cleared, so every element starts from the same baseline; a RESET_SCOPE toggle adds link-decoration removal on top of that once it's set to 'full'. Across 24 sampled frames it moved in 12, covering the largest cumulative area of the nine (19.15%) at an average intensity of 104.5. A click on the scope button counts as the first interaction.

Browser default reset — a starting point that unifies inconsistent spacing and list markers
Open in a new tab

01 · Browser default reset — a starting point that unifies inconsistent spacing and list markers — Measured area 19.15% · moving frames 12/23Original by GODRICH · SCSS and React ship in the zip · See the password

margin: 0list-style: noneRESET_SCOPE
.panel.reset {
  .demoHeading, .demoPara, .demoList { margin: 0 0 $sp-2; }
  .demoList { padding: 0; list-style: none; }
  .demoList li { padding: $sp-1 0; }
}

.panel.reset.full .demoLink {
  color: $subject-blue-light;
  text-decoration: none;
}

02Box model comparison — a card that shows whether border and padding count toward its size

Switching between two sizing modes compares, side by side, how the same width value produces a different actual card size depending on whether border and padding are included. The card re-measures its own border-box and content-box rects on every slider move — it moved in 22 of 23 frames at an average intensity of 122.3, covering 2.36% cumulative area. Dragging the width slider or pressing the pointer down on it both count as interaction.

Box model comparison — a card that shows whether border and padding count toward its size
Open in a new tab

02 · Box model comparison — a card that shows whether border and padding count toward its size — Measured area 2.36% · moving frames 22/23Original by GODRICH · SCSS and React ship in the zip · See the password

box-sizingcontent-boxborder-box
  function apply() {
    borderBox.style.width = declared + 'px';
    contentBox.style.width = declared + 'px';
    // Measured — recompute the actual render result instead of assuming the calculated value
    var bRect = borderBox.getBoundingClientRect();
    var cRect = contentBox.getBoundingClientRect();
    guide.style.transform = 'translateX(' + declared + 'px)';
    widthRange.value = String(Math.round(declared));
    // Theoretical = declared width + border and padding on both sides — shown alongside the measurement (getBoundingClientRect) for comparison
    var theory = declared + 2 * (BORDER_PX + PADDING_PX);
    readout.textContent = 'Declared ' + Math.round(declared) + 'px · border-box actual ' +
      Math.round(bRect.width) + 'px · content-box actual ' + Math.round(cRect.width) +
      'px (theoretical ' + Math.round(theory) + 'px)';
  }

03Heading line-wrap balance — a paragraph that keeps a lone word off the last line

When a long heading wraps across lines, each line's character count balances evenly, preventing a single orphan word from being stranded on the last line. It checks CSS.supports('text-wrap', 'balance') once and only rebalances in a supporting environment; across 24 sampled frames it moved in 23 — tied for the most of any pattern here — at an average intensity of 139.6, the highest of the nine. A click on the toggle counts as the first interaction.

Heading line-wrap balance — a paragraph that keeps a lone word off the last line
Open in a new tab

03 · Heading line-wrap balance — a paragraph that keeps a lone word off the last line — Measured area 3.253% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password

text-wrap: balanceCSS.supportstext-wrap
  function render() {
    var widths = lineWidths();
    var maxW = heading.getBoundingClientRect().width || 1;
    var html = '';
    for (var i = 0; i < MAX_BARS; i++) {
      var w = widths[i] || 0;
      html += '<div class="bar" style="transform: scaleX(' + (w / maxW).toFixed(3) + '); opacity: ' + (widths[i] ? 1 : 0) + '"></div>';
    }
    bars.innerHTML = html;
    readout.textContent = 'Lines ' + widths.length + ' · deviation ±' + Math.round(stdDev(widths)) + 'px';
  }

  function apply(next) {
    on = next && supported;
    heading.style.textWrap = on ? 'balance' : 'wrap';
    toggleBtn.setAttribute('aria-pressed', String(on));
    render();
  }

04Media block baseline — a frame that keeps images and video from overflowing their parent

Images and video, normally treated like inline text with stray gaps beneath them, are set to fill their parent's width while keeping their original aspect ratio, with no overflow. Dragging the frame handle between its minimum and maximum width re-measures both the raw and the fitted image's actual rendered width every frame; it moved in 18 of 23 frames, covering 11.703% cumulative area at an average intensity of 137.6. Pressing down, moving, and releasing the pointer on the handle all count as interaction.

Media block baseline — a frame that keeps images and video from overflowing their parent
Open in a new tab

04 · Media block baseline — a frame that keeps images and video from overflowing their parent — Measured area 11.703% · moving frames 18/23Original by GODRICH · SCSS and React ship in the zip · See the password

max-width: 100%height: autogetBoundingClientRect
  function apply() {
    rawFrame.style.width = frameW + 'px';
    fitFrame.style.width = frameW + 'px';
    // Measured — recompute both images' actual rendered width every frame (the natural 220px width comes from the svg's own width)
    var fitW = fitImg.getBoundingClientRect().width;
    var rawW = rawImg.getBoundingClientRect().width;
    var over = Math.max(0, Math.round(rawW - frameW));
    readout.textContent = 'Frame ' + Math.round(frameW) + 'px · as-is ' + Math.round(rawW) +
      'px (overflow ' + over + 'px) · fitted ' + Math.round(fitW) + 'px';
  }

  var start = 0;
  function loop(ts) {
    if (!start) start = ts;
    if (isDemo && !reduce) { frameW = 180 + 50 * Math.sin((ts - start) / 900); }
    apply();
    requestAnimationFrame(loop);
  }

05Form element font inheritance — a rule that matches buttons and inputs to surrounding text

The separate font family and size that browsers assign to buttons and inputs by default are cleared, so they inherit the exact same typeface, size, and color as the surrounding paragraph. The rule itself is the plainest css reset style in this set — three properties, each set to inherit, and nothing else changes; it moved in 22 of 23 frames, covering 13.463% cumulative area at an average intensity of 95.8. Clicking the toggle, focusing the input, or typing into it all count as interaction.

Form element font inheritance — a rule that matches buttons and inputs to surrounding text
Open in a new tab

05 · Form element font inheritance — a rule that matches buttons and inputs to surrounding text — Measured area 13.463% · moving frames 22/23Original by GODRICH · SCSS and React ship in the zip · See the password

font-family: inheritfont-weight: inheritfont-size: inherit
// The reset's main event — toggling compares the font: inherit family of properties
.wrap.inherited {
  .field, .ghostBtn { font-size: inherit; font-weight: inherit; font-family: inherit; color: $subject-cream; }
}

.readout {
  color: $subject-cream;
  font-size: 11px;
  opacity: .85;
}

06Keyboard focus ring — a border that appears for keyboard navigation but not mouse clicks

The same button shows no border when clicked with a mouse, but a clear focus ring appears when it is reached by pressing Tab, balancing accessibility with visual tidiness. :focus-visible and :focus:not(:focus-visible) split the two input methods without any script reading which device is in use; it moved in 15 of 23 frames — the fewest of the rule-swap cluster — covering 6.39% cumulative area at an average intensity of 94.2. A click, a focus event, or a keydown all count as interaction.

Keyboard focus ring — a border that appears for keyboard navigation but not mouse clicks
Open in a new tab

06 · Keyboard focus ring — a border that appears for keyboard navigation but not mouse clicks — Measured area 6.39% · moving frames 15/23Original by GODRICH · SCSS and React ship in the zip · See the password

:focus-visible:focus:not(:focus-visible)outline-offset
// The main event — :focus-visible only gives a ring for keyboard entry. A mouse-click :focus gets none.
// The outline color transition is smooth, but the ring itself appears and disappears instantly.
.padBtn:focus-visible,
.padLink:focus-visible {
  outline: 3px solid $subject-mint;
  outline-offset: 2px;
  background: rgba($subject-mint, .16);
}

.padBtn:focus:not(:focus-visible),
.padLink:focus:not(:focus-visible) {
  outline: none;
}

07Notch safe-area padding — margin that pushes content clear of a screen's cut-out edges

Inside a frame simulating a screen with a notch and bottom bar, the content's bottom padding automatically grows to match that cut-out area, so no button is ever hidden behind it. A SAFE_AREA_SCALE constant multiplies the simulated inset before it's read back with getComputedStyle; it moved in 23 of 23 frames — tied for the most of any pattern here — covering 1.788% cumulative area at an average intensity of 110.3. Dragging the slider or pressing the pointer down on it both count as interaction.

Notch safe-area padding — margin that pushes content clear of a screen's cut-out edges
Open in a new tab

07 · Notch safe-area padding — margin that pushes content clear of a screen's cut-out edges — Measured area 1.788% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password

env(safe-area-inset-top)env(safe-area-inset-bottom)--safe-top
  function apply(t) {
    // t: 0 to 1, the simulated device's safe-area progress — assigned to the same CSS custom property the real env() variable would set
    var top = Math.round(MAX_INSET_PX * t * SAFE_AREA_SCALE);
    var bottom = Math.round(MAX_INSET_PX * t);
    root.style.setProperty('--safe-top', top + 'px');
    root.style.setProperty('--safe-bottom', bottom + 'px');
    slider.value = String(Math.round(t * 100));
    // Measured — re-reads how many px padding-top actually rendered, via getComputedStyle
    var cs = getComputedStyle(content);
    readout.textContent = 'Top ' + parseFloat(cs.paddingTop).toFixed(0) + 'px · bottom ' + parseFloat(cs.paddingBottom).toFixed(0) + 'px';
  }

08Reduced-motion response — a transition that nearly stops animating based on system settings

Flipping a switch that simulates an environment set to reduce motion changes the same card transition from an elaborate slide-and-fade into something that appears almost instantly. REDUCED_MOTION_SCALE multiplies the base duration down to a tenth when the simulation is on, and a forced reflow makes sure the reset-to-origin state paints before the move starts; it moved in 13 of 23 frames at an average intensity of 122.2, covering 5.87% cumulative area. Clicking the play button, flipping the simulate switch, dragging its slider, and the transition ending all count as interaction.

Reduced-motion response — a transition that nearly stops animating based on system settings
Open in a new tab

08 · Reduced-motion response — a transition that nearly stops animating based on system settings — Measured area 5.87% · moving frames 13/23Original by GODRICH · SCSS and React ship in the zip · See the password

prefers-reduced-motionmatchMediaREDUCED_MOTION_SCALE
  function currentDurationMs() {
    return reduceSim ? Math.round(BASE_DURATION_MS * REDUCED_MOTION_SCALE) : BASE_DURATION_MS;
  }

  function applyDuration() {
    dot.style.transitionDuration = currentDurationMs() + 'ms';
  }

  function play() {
    if (running) return;
    running = true;
    applyDuration();
    dot.classList.remove('at-end');
    // Force a reflow — flush the reset-to-origin state to the screen before starting the move
    void dot.offsetWidth;
    runStart = performance.now();
    dot.classList.add('at-end');
  }

09Rounded corner comparison — a card placing a circular-arc corner next to a smooth squircle corner

Given the same radius value, a card places the default corner that bends sharply along a circular arc right next to one whose curvature changes gradually, making the difference visible at a glance. CSS.supports('corner-shape', 'squircle') gates a single extra rule that layers on top of the shared border-radius both cards already carry; it moved in the fewest frames of any pattern here (9 of 23) at an average intensity of 118.6, covering the smallest cumulative area of the nine (1.044%). A click on the toggle counts as interaction.

Rounded corner comparison — a card placing a circular-arc corner next to a smooth squircle corner
Open in a new tab

09 · Rounded corner comparison — a card placing a circular-arc corner next to a smooth squircle corner — Measured area 1.044% · moving frames 9/23Original by GODRICH · SCSS and React ship in the zip · See the password

corner-shape@supportsCSS.supports
// The main event — border-radius is set identically on both cards from script. Only corner-shape adds one extra line on the right card.
@supports (corner-shape: squircle) {
  .cardShape {
    corner-shape: squircle;
  }
}

How far does a plain css reset go without a framework?

A css reset stylesheet like this one only closes gaps between what environments already do differently by default — it can't add a feature nothing supports, and it can't make a design choice the page itself never made. Pattern 09 makes the boundary concrete: the shared border-radius value is what actually rounds the corner everywhere, and the corner-shape rule only refines how that already-rounded corner curves where it's supported — take the base rule away and there's no fallback left to refine. A reset's whole job ends at "start from the same, known baseline"; what gets built on that baseline is a separate decision every time.

Where it breaks — the trap

The easiest way to break pattern 01 isn't a missing rule, it's the opposite direction: widening RESET_SCOPE to 'full' also strips the underline off every link, and unlike spacing or a list marker, an underline is often the only visual signal that a piece of text is clickable at all. Nothing in this reset adds a replacement affordance back automatically, so a wider scope should ship together with a new link treatment — a border, a color shift, an icon — never on its own, and never assumed "safe" just because the demo panel still reads fine without one.

What reduced motion leaves behind

Three patterns (01, 05, 06) cut only the CSS transition itself under prefers-reduced-motion: reduce; the underlying state — which scope is active, whether the font inherits, which control has focus — still changes exactly as before, it just changes without easing. Pattern 03 is different: reduce stops its own auto-demo loop from re-triggering, but the balance toggle still works the instant you click it. Four patterns (02, 04, 07, 09) never had a transition to remove in the first place — a slider position, a drag position, and a supports() check are all static values, not motion. Pattern 08 is the one exception worth naming directly: reduced motion is what it demonstrates, not a side effect of it, so its matchMedia read and its simulate switch exist specifically to make that duration change visible on demand.

Three variants and accessibility

All nine patterns in this reset live in one zip archive behind a single password, 6tfe57vw, so nothing needs unlocking pattern by pattern.

Variant Changed value Effect
Widen the reset scope 01 RESET_SCOPE minimal → full Setting RESET_SCOPE to full makes clearedList() return CLEARED_FULL (CLEARED_MINIMAL plus link-decoration), so the reset clears four defaults instead of three and link underlines disappear too
Increase the safe-area padding multiplier 07 SAFE_AREA_SCALE 1.0 → 1.5 Raising SAFE_AREA_SCALE to 1.5 multiplies top = MAX_INSET_PX × t × SAFE_AREA_SCALE, so the same slider value pushes the safe-area padding 1.5× wider and the content sits further in
Slow the reduced-motion transition further 08 REDUCED_MOTION_SCALE 0.1 → 0.02 Lowering REDUCED_MOTION_SCALE to 0.02 makes currentDurationMs() return BASE_DURATION_MS × 0.02, so under the reduced-motion simulation the transition shrinks from 1/10 to 1/50 of its length and ends almost instantly

Nine buttons across the set expose their state through aria-pressed rather than color alone, five carry an aria-label for context a sighted label wouldn't otherwise give, and five declare their own :focus-visible outline beyond pattern 06's dedicated one — so a css reset button styles change is announced as a state, not only shown as one. Pair that with what the reduced-motion section above already established, and every one of the nine stays legible to someone who never sees the transition run at all.

FAQ

Is this collection meant to be copied whole, or one item at a time?

Both, in the sense that matters: each of the nine addresses one place environments disagree on their own, un-prefixed defaults, and together they cover spacing, sizing, typography, media, motion, and shape — but nothing stops you from taking just the one pattern you need. Every fence above is copy-paste-ready on its own.

Why do the demos start moving without me touching anything?

Each of the nine runs its own requestAnimationFrame loop guarded by an is-demo class and a live prefers-reduced-motion check, so it animates on its own until you interact with that specific panel — the first pointer, keyboard, or input event on it removes the class and hands control to you alone.

Can I copy just one item's css reset code without the archive?

Yes — every code fence above is already a complete, continuous slice of that item's real source, so pasting one block into a new file reproduces exactly what the live demo does, with no missing setup step hidden anywhere else.

Sources: the all shorthand property, the box-sizing property, the text-wrap property, the prefers-reduced-motion media feature, and the :focus-visible pseudo-class.

Related reading: for more type-focused defaults with nothing extra to load, see CSS Typography Playground: 9 Demos, No Webfonts. Pattern 09 above only compares one radius value across a circular-arc corner and a squircle inside this reset's own context; for nine separate corner-cutting combinations covering that same axis in depth, see CSS corner-shape: 9 Corner Cutting Patterns. And for nine more places environments disagree on their own defaults, see CSS Browser Compatibility: 9 Practical Fallbacks.

Enter the archive password

The password is inside this article. You will find it as you read.