My Modern CSS Reset
Jake Lazaroff
October 29, 2024
How many of us start every web project by copy-and-pasting Eric Meyer's famous 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.
I realized that there's no deep magic; everything a reset does is just normal CSS that you can read and understand.
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.
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:
The Reset
First, everything is in a cascade layer called reset:
This sets the precedence 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:
Next, removing the margins and padding:
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:
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 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:
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:
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 to wrap everything in this selector:
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 ) without forcing myself to "zero out" those styles if those same elements make sense semantically in a different context.
First up, the headings:
The & sigil combines each nested selector with its ancestors.
This is equivalent to selecting :not([class]):where(h1, h2 /* ... */) (and so on) — essentially, any heading without a classname., .
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 in em, so it scales based on whatever font size the element ends up using.
margin-trim: block removes the block dimension margins of elements that touch the edges of their container.
It's a cleaner alternative to zeroing out the top and bottom margins of the first and last children.
line-height: 1.25 sets the leading a little tighter than the rest of the text.
text-wrap: balance is a new value for the text-wrap property that tries to balance the number of characters on each line.
letter-spacing: -0.05ch makes the tracking slightly tighter. It's defined in ch, which scales based on the width of the glyph "0" in the element's font family and size.
Next, paragraphs and lists:
Similar to headings, I set the margin along the block dimension in ems 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 will prevent the margins from combining.
Next, I bring back the list styles I reset earlier:
Let's tackle
or
- and
- 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. I noticed that he added letter-spacing: inherit to his form elements, to which Dan credited an article by Adrian Roselli. Based on that article, I added both letter-spacing: inherit and word-spacing: inherit. October 2024: Based on Mayank's CSS reset, I replaced max-width on media elements with max-inline-size. January 2026: A few big changes: Grouped multiple selectors with :where rather than just listing them out (mostly because it keeps CSS formatters like Prettier from placing them each on their own line). Added interpolate-size: allow-keywords to :root to allow any element on the page to animate to intrinsic sizing keywords. Added margin-trim: block to a few elements.
- 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
Discussion in the ATmosphere