DEV Community

Andy Bell
Andy Bell

Posted on • Originally published at hankchizljaw.com on

A Modern CSS Reset

I think about and enjoy very boring CSS stuff—probably much more than I should do, to be honest. One thing that I’ve probably spent too much time thinking about over the years, is CSS resets.

In this modern era of web development, we don’t really need a heavy-handed reset, or even a reset at all, because CSS browser compatibility issues are much less likely than they were in the old IE 6 days. That era was when resets such as normalize.css came about and saved us all heaps of hell. Those days are gone now and we can trust our browsers to behave more, so I think resets like that are probably mostly redundant.

A reset of sensible defaults

I still like to reset stuff, so I’ve been slowly and continually tinkering with a reset myself over the years in an obsessive code golf manner. I’ll explain what’s in there and why, but before I do that, here it is in its entirety:

/* Box sizing rules */
*,
*::before,
*::after {
  box-sizing: border-box;
}

/* Remove default padding */
ul[class],
ol[class] {
  padding: 0;
}

/* Remove default margin */
body,
h1,
h2,
h3,
h4,
p,
ul[class],
ol[class],
li,
figure,
figcaption,
blockquote,
dl,
dd {
  margin: 0;
}

/* Set core body defaults */
body {
  min-height: 100vh;
  scroll-behavior: smooth;
  text-rendering: optimizeSpeed;
  line-height: 1.5;
}

/* Remove list styles on ul, ol elements with a class attribute */
ul[class],
ol[class] {
  list-style: none;
}

/* A elements that don't have a class get default styles */
a:not([class]) {
  text-decoration-skip-ink: auto;
}

/* Make images easier to work with */
img {
  max-width: 100%;
  display: block;
}

/* Natural flow and rhythm in articles by default */
article > * + * {
  margin-top: 1em;
}

/* Inherit fonts for inputs and buttons */
input,
button,
textarea,
select {
  font: inherit;
}

/* Remove all animations and transitions for people that prefer not to see them */
@media (prefers-reduced-motion: reduce) {
  * {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}
Enter fullscreen mode Exit fullscreen mode

Breaking it down

We start with box-sizing. I just flat out reset all elements and pseudo-elements to use box-sizing: border-box.

*,
*::before,
*::after {
  box-sizing: border-box;
}
Enter fullscreen mode Exit fullscreen mode

Some people think that pseudo-elements should inherit box sizing, but I think that’s silly. If you want to use a different box-sizing value, set it explicitly—at least that’s what I do, anyway. I wrote about box-sizing more over on CSS From Scratch.

/* Remove default padding */
ul[class],
ol[class] {
  padding: 0;
}

/* Remove default margin */
body,
h1,
h2,
h3,
h4,
p,
ul[class],
ol[class],
li,
figure,
figcaption,
blockquote,
dl,
dd {
  margin: 0;
}
Enter fullscreen mode Exit fullscreen mode

After box-sizing, I do a blanket reset of margin and padding, where it gets set by the browser styles. This is all pretty self-explanatory, so I won’t get into it too much.

I will mention the situation with lists, though. I select only lists that do have a class attribute because if a plain ol’ <ul> or <ol> gets used, I want it to look like a list. A lot of resets, including my previous ones, aggressively remove that.

body {
  min-height: 100vh;
  scroll-behavior: smooth;
  text-rendering: optimizeSpeed;
  line-height: 1.5;
}
Enter fullscreen mode Exit fullscreen mode

Next up: body styles. I keep this really simple. It’s useful for the <body> to fill the viewport, even when empty, so I do that by setting the min-height to 100vh. I also like smooth anchor scrolling, so I set scroll-behavior: smooth, too.

I only set two text styles. I set the line-height to be 1.5 because the default 1.2 just isn’t big enough to have accessible, readable text. I also set text-rendering to optimizeSpeed. Using optimizeLegibility makes your text look nicer, but can have serious performance issues such as 30 second loading delays, so I try to avoid that now. I do sometimes add it to sections of microcopy though.

ul[class],
ol[class] {
  list-style: none;
}
Enter fullscreen mode Exit fullscreen mode

Just like the margin and padding rules, I only reset list-style where a list element has a class attribute.

a:not([class]) {
  text-decoration-skip-ink: auto;
}
Enter fullscreen mode Exit fullscreen mode

For links without a class attribute, I set text-decoration-skip-ink: auto so that the underline renders in a much more readable fashion. This could be set on links globally, but it’s caused one or two conflicts in the past for me, so I keep it like this.

img {
  max-width: 100%;
  display: block;
}
Enter fullscreen mode Exit fullscreen mode

Good ol’ fluid image styles come next. I set images to be a block element because frankly, life is too short for that weird gap you get at the bottom, and realistically, images—especially with work I do—tend to behave like blocks.

article > * + * {
  margin-top: 1em;
}
Enter fullscreen mode Exit fullscreen mode

I really like this CSS trick and I’ve finally been brave enough to add it to the reset. The lobotomised owl selector targets direct descendants of an article and adds 1em of top margin to them. This gives a solid rhythm to flow content. I actually use a .flow utility in every project now. You can read more about it on 24 Ways. In fact, I reckon it’s my most used CSS these days.

input,
button,
textarea,
select {
  font: inherit;
}
Enter fullscreen mode Exit fullscreen mode

Another thing I’ve finally been brave enough to set as default is font: inherit on input elements, which as a shorthand, does exactly what it says on the tin. No more tiny (mono, in some cases) text!

@media (prefers-reduced-motion: reduce) {
  * {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}
Enter fullscreen mode Exit fullscreen mode

Last, and by no means least, is a single @media query that resets animations, transitions and scroll behaviour if the user prefers reduced motion. I like this in the reset, with specificity trumping !important selectors, because most likely now, if a user doesn’t want motion, they won’t get it, regardless of the CSS that follows this reset.

ℹ️ Update: Thanks to @atomiks, this has been updated so it doesn’t break JavaScript events watching for animationend and transitionend.

Wrapping up

That’s it, a very tiny reset that makes my life a lot easier. If you like it, you can use it yourself, too! You can find it on GitHub or NPM.

Oldest comments (51)

Collapse
 
nenadra profile image
nenadra

Great list!

I always add:
font-size: 0
to the body element, to disable the gap when you use display: inline-block

Collapse
 
hankchizljaw profile image
Andy Bell

I would only recommend doing hacks like that on the element itself, rather than globally.

Collapse
 
nenadra profile image
nenadra

I use inline-block pretty often. It’s a necessity for me.
But not everybody has the same approach. Thanks for your post again. Very helpful

Collapse
 
westbrook profile image
Westbrook Johnson

Interesting technique. Does that no longer have SEO issues? Maybe the crawler is smart enough to go further into the page, but I remember that being a solid anti-pattern a while back.

Collapse
 
nenadra profile image
nenadra

I don't think so, but should be tested :)
My opinion is that if you put "font-size: 0" to the

element, then you don't plan to have text directly in the body element. The text should be inside of a

,

or other (all with a visible font-size). Nothing is concealed, so it should be crawled normally.

Collapse
 
ryandotfurrer profile image
Ryan Furrer

Thanks for this, Andy! I've been using something similar that I have created, however, it's definitely not as robust. I'll definitely be borrowing some of this!

Collapse
 
joaorr3 profile image
João Ribeiro

Thank you!

Collapse
 
chaofix profile image
Itzik Pop

Amazing work !!! well done

Collapse
 
kathryngrayson profile image
Kathryn Grayson Nanz

This was a very cool read - the "lobotomized owl" in particular was new to me and definitely something I'll incorporate now. Thanks! 😊

Collapse
 
perpetual_education profile image
perpetual . education

It's pretty heavy-handed. If you want to get more granular - check out this type of selector.

article {
  h1 + p {
    margin-top: 20px;
 } 
}
Collapse
 
haroenv profile image
Haroen Viaene

I prefer to keep the default margin / padding on elements, since it requires less CSS to set this back to a real value and I can't e.g. forget to style an element which is added dynamically

Collapse
 
equinusocio profile image
Mattia Astorino • Edited

Does this:

@media (prefers-reduced-motion: reduce) {
  * {
    animation-play-state: paused !important;
    transition: none !important;
    scroll-behavior: auto !important;
  }
}

can just be like this?

* {
  animation: unset | revert | initial !important;
  transition: unset | revert | initial !important;
  scroll-behavior: unset | revert | initial !important;
}
Collapse
 
hankchizljaw profile image
Andy Bell

It's in the media query for if someone prefers reduced motion. Your code will just disable animations globally.

Collapse
 
equinusocio profile image
Mattia Astorino • Edited

Yes sorry, i forgot to add the @media query. By point was about the values (unset, revert, initial). But i think forcing specific value is more stronger, while my proposed values are not predictable.

Thread Thread
 
hankchizljaw profile image
Andy Bell

Yeh, sure you can do that!

Collapse
 
maxart2501 profile image
Massimo Artizzu

Very nice, Andy. I'll consider using it from now on 👍

I was wondering about the box-sizing setting selector: isn't *::before equivalent to just ::before?
I'm asking because I've seen that everywhere with the asterisk, so I wonder if there are any differences I'm missing out.

Collapse
 
hankchizljaw profile image
Andy Bell

Both will work fine! It's just a stylistic choice, really.

Collapse
 
sandstedt profile image
Jonas Sandstedt

Thanks for a great reset! Really liked the smart list styling.

But can't agree with the prefers reduced motion part. This option is for people who wants reduced motion. Not no motion at all. A lot of small animation can be critical to understanding the flow or function, without triggering motion sickness or nausea.

Also opacity and color changes usually works just fine to.

Well I could rant over this for hours, but I guess you get my point 😄. Devs always need to add this media query on problematic animations (also in JS code), but that must be up to the author of the code to decide.

Collapse
 
hankchizljaw profile image
Andy Bell

I disagree. The great thing about it being OS is that you can fork it and remove the bits you don't like 🎉

Collapse
 
sandstedt profile image
Jonas Sandstedt

Absolutely. Just know how copy paste friendly this community is, many developers will just take it and don't think what it does. Just wanted to make sure people know what it does.

But will definitely try this on my next project. Thanks!

Collapse
 
larsklopstra profile image
Lars Klopstra ⚡

How about adding an overflow-x: hidden to the body?

Collapse
 
hankchizljaw profile image
Andy Bell

I'm up for that. Great idea!

Collapse
 
hellojere profile image
Jere Salonen

Not a great idea! This'll kill position: sticky for all the child elements.

Thread Thread
 
hankchizljaw profile image
Andy Bell

Oooh good point!

Thread Thread
 
chriscoyier profile image
Chris Coyier

The mental leap for that is a tough one for me. Why isn't my position: sticky; working?! Oh, because some parent element has hidden X overflow? 🤯

Thread Thread
 
hankchizljaw profile image
Andy Bell

Yup. I completely forgot about that bugger. Definitely not helpful to be set as a global style in that context.

Thread Thread
 
igcorreia profile image
Ignácio Correia

Finally I figure it out too :)

Thread Thread
 
cullylarson profile image
Cully Larson

Apparently if you set the height of the element you're overflow hidden'ing on, sticky will work. So something like:

#container {
    overflow-x: hidden;
    overflow-y: scroll;
    height: 100vh; /* need to set this for child elements to be able to use display:sticky (c.f. https://github.com/w3c/csswg-drafts/issues/865) */
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
lewiscowles1986 profile image
Lewis Cowles

Just be careful with CSS and contain within something else overflow-x: hidden; as needed. Generic reset rules about body overflow just paper over cracks

Some comments may only be visible to logged-in visitors. Sign in to view all comments.