Hey folks! Elad reached out to me to show me his new CSS reset project called the-new-css-reset. It’s quite interesting! I thought a neat way to share it with you is not only to point you toward it, but to ask Elad some questions about it for your reading pleasure.
Here’s the entire code for the reset up front:
/*** The new CSS Reset - version 1.2.0 (last updated 23.7.2021) ***/
/* Remove all the styles of the "User-Agent-Stylesheet", except for the 'display' property */
*:where(:not(iframe, canvas, img, svg, video):not(svg *)) {
all: unset;
display: revert;
}
/* Preferred box-sizing value */
*,
*::before,
*::after {
box-sizing: border-box;
}
/*
Remove list styles (bullets/numbers)
in case you use it with normalize.css
*/
ol, ul {
list-style: none;
}
/* For images to not be able to exceed their container */
img {
max-width: 100%;
}
/* Removes spacing between cells in tables */
table {
border-collapse: collapse;
}
/* Revert the 'white-space' property for textarea elements on Safari */
textarea {
white-space: revert;
}
Hey Elad! I want to get to the code bits really quick here, but first, I’d love to hear the why about this project. CSS resets have been popular for ages because they wipe out the differences that exist between different browsers default styles and then you build up your styles from there. There are some widely-used resets out there already — why a new one?
First, when talking about “CSS resets” we have two approaches:
- Nicolas Gallagher’s Normalize.css is the gentle approach. Normalize.css is fixing differences between the implementation in different browsers.
- Eric Meyer’s CSS Reset is the hard approach, saying that in most cases we don’t want basic styles from the browsers, like the
font-size
value we get from elements like<h1>
through<h6>
, or the default styles for the<ul>
and<ol>
list elements. For example, we use the list only for the semantic meaning, and because it helps in other ways for accessibility and SEO.
I love Normalize.css. I think it’s a must-have in any project, even if you prefer the CSS Reset idea.
And why is Normalize.css so important? Normalize.css touches shadow DOM elements that the CSS Reset doesn’t. When looking at Normalize.css, you will find special pseudo-classes like ::-moz-focus-inner
, ::-webkit-file-upload-button
, and more. It covers so many bases and that’s why I believe Normalize.css is a must-have in any project.
I love the hard CSS Reset as well. I think in most cases we don’t want the basic styles of the browser, and if we need it in a specific place, we will define it according to our need. This brings me to the point that I’m using both Normalize.css and CSS Reset combined. So, Normalize.css is first to load, followed by the hard CSS Reset.
So, why we need a new CSS reset? The CSS resets we have are built on old CSS features. But in the last several years, we’ve gotten new features built specifically for resetting things in CSS, and this got me thinking that now we can create a much more valid CSS reset using these new cutting-edge CSS features.
all: unset;
. That’s what is doing the heavy lifting in this CSS reset yes? How does that work?
It seems to me the juiciest bit here is that very first ruleset. Let’s start with that first CSS property and value: all
is the most exceptional CSS property because it allows us to reset all the properties that exist in the CSS all at once.
The property accepts several keywords. The two basics are initial
and inherit
; there are two smarter ones, which are unset
and revert
. To understand what all: unset
does, we need to jump to the fundamental behavior of our CSS properties.
In CSS, we have two groups of properties:
- Inherited properties group: These are properties that have inheritance by default — mainly typography properties.
- Non-inherited properties group: These are all other properties that don’t inherit by default, for example, the Box Model properties that include
padding
,border
, andmargin
.
Like typography properties, we want to keep the inherit
behavior when we try to reset them. So, that’s where we’re able to use the inherit
keyword value.
/* Will get values from the parent element value */
font-size: inherit;
line-height: inherit;
color: inherit;
For the other properties in the non-inherited properties group, we want to get their initial value in most cases. It is worth mentioning that the initial
keyword computes differently for different properties.
max-width: initial; /* = none */
width: initial; /* auto */
position: initial; /* = static */
After we understand the fundamentals as well as the inherit
and initial
keyword values, we understand that if we want to reset all of properties together, we can’t use them directly with the all
property. That’s because, if we reset all of the properties to the initial value, i.e. all: initial
, we lose the inherent behavior on the inherited properties group. And suppose we reset all properties with the inherit
value. In that case, all the properties get an inheritance — even Box Model properties, which we want to avoid.
That’s why we have the unset
value. unset
resets the property according to its type. If we use it on an inherited property, it’s equal to inherit
; if we use it on a natural non-inherited, it equals initial
.
max-width: unset; /* = initial = none */
font-size: unset; /* = inherit = get parent element value */
This brings us back to the main feature of my CSS reset. What all: unset
does is reset all the inherited properties to the inherit
value, and all the other properties in the non-inherited properties group to their initial
value.
This operation removes all the default user-agent-stylesheet styles that the browser is adding. To understand these substantial new CSS powers, all of this happened while I was doing only one operation to all HTML elements.
/*
Reset all:
- Inherited properties to inherit value
- Non-inherited properties to initial value
*/
* {
all: unset;
}
display: revert;
— does all: unset;
do things to the display
property that would be undesirable?
And then you follow it up with Short answer: yes. The display
property represents the basic structure which we do want to get from our user-agent stylesheet. As we saw in most of our properties, the unset
value is doing an excellent job for us, and we reset all properties in one operation.
Now, to understand what the unique revert
keyword value is doing for the display
property, let’s talk about the two types of styles that we are getting from our browsers. The styles we are getting from our browsers are built from two layers:
- Layer 1, the CSS
initial
value: As we already saw, the first layer is theinitial
values of all our properties in CSS, including theinherit
behavior on some of the properties. - Layer 2, the user-agent stylesheet: These are the styles that the browser defines for specific HTML elements.
In most cases, when we want to reset things, we want to remove the basics styles of Layer 2. And when we do reset with all: unset
, we remove all the styles of the user-agent stylesheet.
But the display
property is exceptional. As we already saw, every property in CSS has only one initial value. This means that if we reset the display
property to its initial, like on a <div>
element or any other HTML element, it always returns the inline
value.
Continuing with this logic, we connect the <div>
element to the default display: block
declaration, which we get from browsers. But we only get this behavior because of Layer 2, the user-agent stylesheet, which defines them. It’s built on the same idea that the font-size
is bigger on heading elements, <h1> to <h6>, than any other HTML elements.
div {
display: unset; /* = inline */
}
span {
display: unset; /* = inline */
}
table {
display: unset; /* = inline */
}
/* or any other HTML element will get inline value */
This is, of course, unwanted behavior. The display
property is the only exception we want to get from our browser. Because of that, I’m using the unique keyword value revert
to bring back the default display
value from the user-agent stylesheet..
The revert
value is unique. First, it checks if there is a default style for the specific property in the user-agent stylesheet for the specific HTML element it is sitting on, and if it finds it, it takes it. If it doesn’t find it, revert
works like the unset
value, which means that if the property is an inherited property by default, it uses the inherit
value; if not, it uses the initial
value.
*
), but then removing a handful of things. Is that right? Why target those specific things?
Then those two rules are within a ruleset with a selector where you select almost everything. It looks like you’re selecting everything on the page via the universal tag selector (When I started to imagine “The New CSS Reset” I didn’t think I would need exceptions. It was a lot more straightforward in my imagination.
🤓 This is how I imagine #CSS Reset in 2022.
— Elad Shechter (@eladsc) February 2, 2021
What do you think?
More about CSS Resets, you can read my last article on "How Does CSS Work?":https://t.co/r3ZFz4wl6j pic.twitter.com/kce8wbqmOB
But when I started to create experiences, I was replacing my old CSS reset with my new CSS reset (without all the exceptions), and I saw some things that broke my old projects, which I tested.
The main things that broke were elements that can get sizes via width
and height
attributes — elements like <iframe>
, <canvas>
, <img>
, <svg>
, and <video>
. Unfortunately, when I reset everything, the width and height of those elements are defined by the auto
value, which is stronger and removes the effect of the elements’ width
and the height
attributes.
This can be problematic because we want the exact size to come from the HTML element in cases where we add the dimensions via the HTML width and height attributes. We prefer to get it from the HTML, not from the CSS, because when it comes from the CSS, it can cause glitches when the page is loading.
The only way I found to remove the reset effect for all those particular elements is to put them under the :not()
selector. In this case, my new CSS reset is harmful and not helpful, and because of that, I removed the effect for these specific elements.
:where()
?
Keeping specificity at a minimum seems important in a reset, so you don’t find yourself fighting the reset itself. Is that the idea behind Yes, the idea of the :where()
is to remove the specificity. We don’t need to describe more significant specificity in our CSS only to override the CSS reset.
In general, I think we will soon see a lot more cases of :where()
wrapping things to remove their specificity, and not only to replace multiple selectors.
<svg>
is in there. What is that about?
It looks like some extra special care for children of The second case, :not(svg *)
is done with a separate :not()
only because it is for a different issue. Touching the inner elements of an SVG can break the visual image, and this is one of those things that there isn’t any reasonable cause to interrupt the browser.
Let the image be an image. I say.
box-sizing
, but you’re changing it anyway. I’m a fan of that one myself, but I’m curious about the philosophy of what goes into a reset and what doesn’t.
After the big resetting part, it goes into some bits that are more opinionated. For example, there are no browser disagreements about the initial value of In general, when it comes to a CSS reset, I think it is an opinion thing. For example, Eric Meyer’s CSS Reset chooses to remove the styles of specific things, and other things like the display
property, are uninterrupted, which as you already saw, I totally agree with.
About box-sizing
, yes, that is opinionated. I have been a web developer for 15 years. In that time, I’ve seen many web developers struggling to understand the default behavior of box-sizing
, which I got so used to in the past. When there were talking about adding it to the CSS Reset many years ago, web developers, many of whom had been in the industry for a long time, were afraid of this change because, in general, people are scared of change.
But these days, I almost do not see any project that isn’t resetting all elements to box-sizing: border-box
. A browser’s engines can’t fix the default awkward behavior of the default box-sizing: content-box
, because if they do so, they will break support for older websites. But for newer projects, including this piece is a must since we’re left to solve it on our own.
And again, this is totally opinionated.
list-style: none;
wipes out the semantics of a list, as well on iOS. Any concerns there?
Two other rulesets, the removing of list styles and collapsing borders, are also in the Eric Meyer’s reset, so they have been around a long time! Starting with the list styles, I can see wanting to wipe those out as lists are often used for things that don’t need a marker, like navigation. But it feels a bit contentious these days, as The short answer: no. No concerns on my end. Here’ why.
If we choose not to reset list-style
, it means we can’t use list elements for navigation. This also means that we won’t get any semantics for any other browsers.
And now, if I need to choose between most browsers gaining these semantics, and no browsers gaining these semantics, I’m choosing the former, as more browsers gain from it than they lose.
max-width
on images feels like that to me. Again, it’s not something browsers disagree on now, but also something that pretty much every project does.
Can you see yourself adding to this over time? Like if you find yourself doing the same things on projects over and over? Setting the Of course. If this reset is missing something that I didn’t consider, I will add it and release a new version. But it needs to be like your example of max-width
where there is no good case where we want an image to overflow its container.
Cascade Layers stuff? Any thoughts on how that might factor in to CSS resets down the road?
Have you seen this newI didn’t think about it until you asked me. The Cascade Layers module is an exciting feature. It still doesn’t have any support, but most browser engines have already put this feature under a flag, and this means that there is a good chance that we will see this feature one year from now supported in all evergreen browsers.
For those who haven’t heard about Cascade Layers yet, the idea is that @layer
can override styles without creating stronger specificity because every layer that loads after it is automatically stronger than the previous layers.
When this feature arrives, we will load the “reset” layers first. For example: first, Normalize.css, then the-new-css-reset, and then the @layer
styles of the project.
@layer normalize; /* Create 1st layer named “normalize” */
@layer the-new-css-reset; /* Create 2nd layer named “the-new-css-reset” */
@layer project-styles; /* Create 3rd layer named “project-styles” */
This should make sure that the bottom layer always beats the top. This also means that removing specificity with :where()
, like I did, will no longer be necessary.
@layer
is one of the most exciting future features coming to CSS, thanks to Miriam Suzanne, who is doing, as always, a fantastic job.
I was skeptical when I clicked the link but I’m glad I did. I think I will try this in an upcoming project.
Pure gold. Not only is this an awesome reset option, I finally understand the reset value. THANK YOU
Wow, I think I learned more about CSS from this one article than I have in years. Thanks! ✌️
This is totally awesome!
I’ve been using the Eric Meyer reset since it came out, and more recently Andy Bell’s reset.
I like how Elad is happy to “remove” list semantics in iOS Safari for the sake of other (better) browsers that handle the missing bullets more gracefully. ;)
Thanks for the informative interview. I used to confuse the effects of
unset
andrevert
. With the example provided in the interview, especially the diagram, they are clear to me now.Also thanks for the information of
list-style: none;
wipes out the semantics of a list, which I didn’t know. So I tested a different approach of hiding list markers with::marker { color: transparent; }
, and it looks like the approach worked. That said, I’m not sure if the approach is detrimental to accessibility, too. Any advice would be appreciated.After a bit more testing, I found that
font-size: 0
is needed as well otherwise markers would still take up spaces.Ian, you might look at how Andy Bell approaches this. He targets lists that have the attribute
role="list"
explicitly set:/* Remove list styles on ul, ol elements with a list role, which suggests default styling will be removed */
ul[role='list'],
ol[role='list'] {
list-style: none;
}
See https://piccalil.li/blog/a-modern-css-reset/
I once documented on stackOverflow an issue with the css
all
property – specifically on iOS and how it effects color..I currently don’t have an iOS device on hand to check this, but i’m wondering if this is still an issue and if it is – if it can express itself within the new css reset code
This is awesome. At pains of adding more stuff to it, I think it needs
outline: revert
added into the top declaration.Unsettling unset.
I tried this rest and ‘puff’ my input[type=”raio”] had disappeared due to
Yes Jame, and this is part of the main idea, design your own styles even for radio and checkbox buttons.
You can design checkbox for example:
And if you don’t want the reset on them, you can revert the input element back, for example:
I have to confess that I never thought to use
all
like the “normal” css property that it is.The only situation where I used
all
was in transitions (and even there I only made rare use of it).This article just changed my view on this property. I’m definitely gonna use this reset sheet along with Heydon Pickering’s Lobotomized Owl Selector in my next project :D
Playing with this and found it obliterates standard
<pre>
white-space
formatting, requiring an extrarevert
OR
(
white-space: pre
sauce: https://webmasters.stackexchange.com/a/16597)Fair warning,
is not the same as if there was nothing at all. I was making a font demo with features applied to
<span>
s sprinkled within text. The first rule messed up the hyphenation and positioning of some of these spans (see last line on the image). The second rule didn’t fix that, and I was unable to debug this as the issue was only present in Chrome, which no longer allows showing UA styles.Seems Markdown is not all that supported :) Here’s the image link:
Hi Adam,
Please add CodePen with the specific problem. Then, it will be easier to see the problem and play with the code to understand if there is a problem. And if there is to find a way to solve it :-).
Here’s an example that
all: revert
doesn’t work as expected: https://codepen.io/hyvyys/pen/GRMbbevA
contenteditable
div is no longer editable, at least in Chrome. In Firefox it is editable but no caret is visible.Hi Adam,
You kind of resetting my CSS reset, if you put all:revert; for every DIV.
If you add all:revert; without my CSS reset the contenteditable won’t work.
The contenteditable specific getting separate fix to bring it back, as you can see in the CSS reset:
/* revert for bug in Chromium browsers
– fix for the content editable attribute will work properly. */
:where([contenteditable]){
-moz-user-modify: read-write;
-webkit-user-modify: read-write;
overflow-wrap: break-word;
-webkit-line-break: after-white-space;
}
Oh, right, it wasn’t in the original version that I had copied (and is not in this article).
Here’s the replication of my original issue: https://codepen.io/hyvyys/pen/poWMgMj. The hyphenation is off in Chrome.
I wonder how you go about fixing these things! Since DevTools don’t list all the properties overridden by
all
(and don’t list all vendor-prefixed properties in the Computed tab), do you just go with an index of all properties and check them one by one, or just are a genius like that? :)Thanks!
And what about
margin:0
padding: 0
here
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
?
The
all:unset
is already removing the padding and margin from all other elements.I think this is an interesting approach but it still leaves me wondering why I would do this over just using say Normalize or Sanitize. (The latter actually includes Normalize as part of it.)
I understand the purpose of the hard reset for sure. But I just wonder how necessary that truly is with modern browsers and why the more opinionated approaches of Normalize or Sanitize aren’t better.
I feel like that’s one question that would be worth asking but wasn’t really asked here. I do see Elad’s description of how he prefers the hard and gentle approach. But what’s not clear is why the hard approach is really needed at all if you’re going to use the gentle approach anyway.
I think part of this is my natural inclination, not always well-founded, to avoid things that do “all” or “never” style blanket changes to everything.
Elad says: “The CSS resets we have are built on old CSS features. But in the last several years, we’ve gotten new features built specifically for resetting things in CSS…”
This would imply that clearly the CSS maintainers feel that the resets are necessary if we’re getting new features specifically to reset things. But then why are so many people conflicted on whether to use resets at all?
All this said, I do agree that if the goal is to do a hard reset of everything, this definitely seems like the most concise way to do it.
As a final point, if I was using Normalize or Sanitize with this approach, it’s very unclear to my why I would put those BEFORE the hard reset. It seems the hard reset would just then remove whatever Normalize/Sanitize puts in place that isn’t just Shadow DOM related.
In the article Elad states:
“The second case, :not(svg *) is done with a separate :not() only because it is for a different issue. Touching the inner elements of an SVG can break the visual image, and this is one of those things that there isn’t any reasonable cause to interrupt the browser.”
It is entirely unclear to me what that means. Further, in the actual GitHub file, that latter clause is given as:
:not(svg *, symbol *))
And the comment is the “symbol *” part is to solve a “Firefox SVG sprite bug.” But it’s hard to determine what the bug being referred to actually is. In looking things up, it looks like Firefox has had SVG sprite issues for awhile.
I feel like resets or even normalize type stuff should reference specific bug numbers or at least some reference that says exactly what’s being solved by the inclusion. I say this because I feel like a lot of stuff in various resets is taken on faith and then propagates for a long time.
I’m not saying that’s the case here. I just have no way to know. Case in point is I see a lot sources quoting this reset but none of them actually seem to know exactly why that particular bit is in there.