My Modern CSS Reset
How many of us start every web project by copy-and-pasting Eric Meyer’s famous CSS reset CSS Tools: Reset CSS meyerweb.com/eric/tools/css/reset/ ?
I did that for a long time without even really reading it. I knew that it smoothed over some of the inconsistencies between various browsers, and that was enough for me.
Then, a few years ago, I read a blog post by Josh W Comeau called My Custom CSS Reset My Custom CSS Reset I have a set of baseline CSS styles that come with me from project to project. In the past, I'd use a typical CSS reset, but times have changed, and I believe I have a better set of global styles! www.joshwcomeau.com/css/custom-css-reset/ . I realized that there’s no deep magic; everything a reset does is just normal CSS that you can read and understand.1
My CSS reset is loosely based on Josh’s, but it takes a slightly more opinionated stance and applies some light default styling that I end up adding to almost every project anyway. CSS has also rapidly improved over the past few years, and I’ve tried to take advantage of that by including a few modern features like cascade layers, logical properties, nicer text wrapping and nesting.2
I suppose it kind of blurs the line between “CSS reset” and “classless CSS framework”. My goal isn’t really to adhere to a strict definition of either one; I’ve just found this set of styles to be a good starting point for most websites I build.
If you haven’t read Josh’s post, go do that now. He explains his reset in detail, with a bunch of great interactive examples. I’m going to talk mostly about the points at which my reset differs from his.
Here it is:
@layer reset {
*, *::before, *::after {
box-sizing: border-box;
}
* {
margin: 0;
padding: 0;
}
body {
line-height: 1.5;
}
img, picture, video, canvas, svg {
display: block;
max-inline-size: 100%;
}
input, button, textarea, select {
font: inherit;
letter-spacing: inherit;
word-spacing: inherit;
color: currentColor;
}
p, h1, h2, h3, h4, h5, h6 {
overflow-wrap: break-word;
}
ol, ul {
list-style: none;
}
:not([class]) {
h1&, h2&, h3&, h4&, h5&, h6& {
margin-block: 0.75em;
line-height: 1.25;
text-wrap: balance;
letter-spacing: -0.05ch;
}
p&, ol&, ul& {
margin-block: 1em;
}
ol&, ul& {
padding-inline-start: 1.5em;
list-style: revert;
}
li& {
margin-block: 0.5em;
}
}
}
The Reset
First, everything is in a cascade layer Cascade layers - Learn web development | MDN If you understood most of this article, then well done — you're now familiar with the fundamental mechanics of CSS cascade layers. Next up, we'll look at the box model in detail. developer.mozilla.org/en-US/docs/Learn/CSS/Building_blocks/Cascade_layers called reset
:
@layer reset {
/* ... */
}
This sets the precedence Cascade layers - Learn web development | MDN If you understood most of this article, then well done — you're now familiar with the fundamental mechanics of CSS cascade layers. Next up, we'll look at the box model in detail. developer.mozilla.org/en-US/docs/Learn/CSS/Building_blocks/Cascade_layers#determining_the_precedence_based_on_the_order_of_layers of the reset.
Briefly: normal styles (i.e. not !important
) outside of layers have precedence over normal styles in layers.
The order of precedence for layers is determined by the order in which they’re declared, with later layers overriding earlier ones.
Generally, resets are placed first in a stylesheet, which means that any other styles will be able to easily override these.
Note that the layer can also be declared at import time rather than as a block:
@import url("reset.css") layer(reset);
Next, removing the margins and padding:
* {
margin: 0;
padding: 0;
}
Josh only removes the margins; I remove the padding as well. There aren’t many elements that I use without overriding the built-in padding — and as we’ll see later, I end up adding it back in some cases.
Next, media elements:
img, picture, video, canvas, svg {
display: block;
max-inline-size: 100%;
}
Like Josh, I set them to display: block
to avoid that weird extra vertical space.
Instead of max-width
, though, I use max-inline-size
: a logical property CSS logical properties and values - CSS: Cascading Style Sheets | MDN The CSS logical properties and values module defines logical properties and values that can control layout through logical rather than physical direction and dimension mappings. Logical properties define direction‐relative equivalents to their corresponding physical properties. developer.mozilla.org/en-US/docs/Web/CSS/CSS_logical_properties_and_values which controls layout based on the direction of the text.
It sets the maximum size along the inline dimension.
For horizontal languages like English, that’s width, meaning it functions exactly the same as max-width
— but for vertical writing modes (such as in some Asian languages) it instead functions as max-height
.
Next, form controls:
input, button, textarea, select {
font: inherit;
letter-spacing: inherit;
word-spacing: inherit;
color: currentColor;
}
This is another tweak to Josh’s styles.
While font: inherit
makes these elements inherit the font-family
, font-size
, etc, from their parent, it doesn’t touch the color (which defaults to black) or the letter or word spacing.
Setting letter-spacing
and word-spacing
to inherit
makes them inherit the corresponding properties from their parent, while setting color
to currentColor
makes them also take on their parent’s text color.
One notable place where Josh’s reset diverges from Eric Meyer’s is in resetting list styles:
ol, ul {
list-style: none;
}
I find that I remove the list styles more often than not, so it’s included in the reset. Again, we’ll later see the list styles reapplied in some cases.
The Framework
Here’s where this reset crosses over into “classless CSS framework”. I’m going to start adding back styles to the page, using CSS nesting CSS nesting - CSS: Cascading Style Sheets | MDN The CSS nesting module defines a syntax for nesting selectors, providing the ability to nest one style rule inside another, with the selector of the child rule relative to the selector of the parent rule. developer.mozilla.org/en-US/docs/Web/CSS/CSS_nesting to wrap everything in this selector:
:not([class]) {
/* ... */
}
All following selectors are nested inside this block, which selects every element without a class.
The idea is that if I add a class to an element, it’s probably because I want to customize that element specifically, rather than using the base styles.
The :not([class])
selector lets me add that base set of styles to elements I often use without classes (such as <p>
or <li>
) without forcing myself to “zero out” those styles if those same elements make sense semantically in a different context.
First up, the headings:
h1&, h2&, h3&, h4&, h5&, h6& {
margin-block: 0.75em;
line-height: 1.25;
text-wrap: balance;
letter-spacing: -0.05ch;
}
The &
sigil combines each nested selector with its ancestors.
This is equivalent to selecting h1:not([class])
, h2:not([class])
and so forth.3
Here are the properties, one by one:
margin-block: 0.75em
is another logical property. It sets the margins along the block dimension, which for horizontal languages means top and bottom. It’s defined inem
, so it scales based on whatever font size the element ends up using.line-height: 1.25
sets the leading a little tighter than the rest of the text.text-wrap: balance
is a new value for thetext-wrap
property CSS text-wrap: balance | CSS and UI | Chrome for Developers A classic typography technique of hand-authoring line breaks for balanced text blocks, comes to CSS. developer.chrome.com/docs/css-ui/css-text-wrap-balance that tries to balance the number of characters on each line.letter-spacing: -0.05ch
makes the tracking slightly tighter. It’s defined inch
, which scales based on the width of the glyph “0” in the element’s font family and size.
Next, paragraphs and lists:
p&, ol&, ul& {
margin-block: 1em;
}
Similar to headings, I set the margin along the block dimension in em
s to bring back some vertical spacing proportional to the font size.
Since most of these elements will be adjacent to others of the same type, 1em
might seem like a lot — but margin collapse Mastering margin collapsing - CSS: Cascading Style Sheets | MDN The top and bottom margins of blocks are sometimes combined (collapsed) into a single margin whose size is the largest of the individual margins (or just one of them, if they are equal), a behavior known as margin collapsing. Note that the margins of floating and absolutely positioned elements never collapse. developer.mozilla.org/en-US/docs/Web/CSS/CSS_box_model/Mastering_margin_collapsing will prevent the margins from combining.
Next, I bring back the list styles I reset earlier:
ol&, ul& {
padding-inline-start: 1.5em;
list-style: revert;
}
li& {
margin-block: 0.5em;
}
Let’s tackle <ol>
and <ul>
first:
padding-inline-start: 1.5em
adds padding at the beginning of the inline dimension, which for left-to-right langauges means left. This indents each list item.list-style: revert
adds back the default list marker.
Finally, for <li>
I add back some margin along the block axis for vertical rhythm.
That’s it! As with Josh’s reset (and Eric’s before his), feel free to use or modify this for your own projects; consider it public domain. If there’s anything you think should be added, removed or changed, or any tips I’ve missed or new features that might improve this, I’d love to hear about it.
Changelog
- October 2024: Immediately after publishing, Dan Burzo replied to me with his own CSS reset reset.css · Dan Cătălin Burzo better default styles danburzo.ro/snippets/css-reset/ .
I noticed that he added
letter-spacing: inherit
to his form elements, to which Dan credited an article by Adrian Roselli Under-Engineered Text Boxen Others in this sorta-series: Under-Engineered Custom Radio Buttons and Checkboxen Under-Engineered Toggles Under-Engineered Toggles Too This is the latest, and not last, in my informal series of posts on under-engineered controls. Generally I am looking at the minimum amount of CSS necessary to style native HTML controls while also retaining… adrianroselli.com/2019/09/under-engineered-text-boxen.html . Based on that article, I added bothletter-spacing: inherit
andword-spacing: inherit
. - October 2024: Based on Mayank’s CSS reset @acab/reset.css a modern css reset for 2024 and beyond. Latest version: 0.10.0, last published: 5 months ago. Start using @acab/reset.css in your project by running `npm i @acab/reset.css`. There are 2 other projects in the npm registry using @acab/reset.css. www.npmjs.com/package/@acab/reset.css , I replaced
max-width
on media elements withmax-inline-size
.
Footnotes
-
To be fair to myself, when I started web development in the age of Internet Explorer 6, I was in high school and browser inconsistencies were really bad. At the time, getting websites to work across browsers really did involve taming a bunch of dragons. ↩
-
Keep in mind that for any newer CSS feature, you should check support statistics somewhere like Can I Use Can I use... Support tables for HTML5, CSS3, etc caniuse.com before deciding to add it to your own website. ↩
-
Technically,
&
gets replaced with its ancestor selectors inside of:is()
, so:not([class]) { h1& }
translates toh1:is(:not([class]))
. In this case, that’s the same thing ash1:not([class])
, but that’s not the case for every nested selector. ↩