Normalize and reset CSS as a base, not an extra

In all of my coding, I like to get as far down to the fundamentals as I can, so it’s no wonder I am, perhaps neurotically, endlessly tinkering with CSS resets and normalization. If you don’t do something, you’re going to end up with surprises: iOS Safari may do one thing, that old version of Internet Explorer does another, all of them add padding and margins—to some extent, using wonderful tools such as Eric Meyer’s CSS Reset or Nicolas Gallagher and Jonathan Neal’s Normalize is a safety measure, and makes our work as CSS developers more predictable. It’s important here to differentiate a reset—which overrides the default stylesheets that come with every browser, and are why a plain HTML file will have padding and margins and bold headlines and so on—and the idea of Normalize.css, which goes after browser-specific behavior to avoid weird quirks. I like and use both approaches.

The problem here is you end up with a lot of code. Often these set-ups get added to the top of a stylesheet and forgotten about, where all their rules are used whether they’re needed or not, and left behind even if they’re overridden by the code we’re writing further down the page.

I’ve tried a few custom approaches in the last couple years, starting with Another CSS Reset, which has my own “Scalpel.css” update to the Meyer reset, and the more evolved Setup.css, and there are other new variations out there including CSS Remedy. I have been rethinking my own stuff, and I have a better way forward, at least for my own work: resets and normalization as a base, not an extra. The key to this is thinking in terms of object-oriented CSS and leveraging the power of gzip.

With Normalize.css and the Meyer reset, just to name two, we have what I would call rules-first CSS, where we group multiple elements around single rules to keep our code DRY—”don’t repeat yourself.” Ah, but we do. If we have a normalization like:

article, aside, footer, header, nav, section, menu {
  display: block;
}

then ultimately, we’re probably going to need to call up these elements again to give them other properties:

article, aside, footer, header, nav, section, menu {
  display: block;
}

article {
  max-width: 80%;
  margin: 0 auto;
  /* you get the idea */ 
}

I would say this is problematic: we no longer have a single source of truth for our elements, and we’re inclined not to pay attention to all this stuff at the top. It encourages lazy CSS writing and potential confusion about what rules are coming from where. This is the kind of thing that makes people confused about writing CSS in the endless and baffling Twitter JS-vs.-CSS wars.

Instead, you might have a more object-oriented, or element-first design:

article {
  display: block;
  max-width: 80%;
  margin: 0 auto;
}

aside {
  display: block;
  /* other aside code */
}

footer {
  display: block;
  /* other footer code */
}

Now we’re repeating rules. Isn’t that bad? From a performance standpoint, not really: You can write display: block; in your CSS 1 time, 100 times, or 1000 times and Gzip will crunch it down when the server delivers the file to your users. And now we can state each element a single time in our entire CSS and be sure we know exactly what it’s doing. This means we can start from a set of defaults and, if we don’t need that element or normalization for our site, when we get down to that part of the code, we can cut it out.

There are a couple of other ways we can optimize this, starting with Sass mixins to avoid, yes, repeating ourselves:

@mixin reset-display() {
  display: block;
}

article {
  @include reset-display;
  max-width: 80%;
  margin: 0 auto;
}

aside {
  @include reset-display;
  // other aside code
}

footer {
  @include reset-display;
  // other footer code
}

Now our code is rule-first and element-first: we’re controlling the reset rule from a single line of code like we were before, but we also get to choose for each element if we need it or not.

I am also a prolific documenter and comment-writer, and it seems sensible to me to note what each rule is doing (is it a reset, is it normalizing, what browsers is it normalizing if so) and be sure to leave a note about these browser defaults if we do overwrite or replace the rule.

For instance, as a default style, we might have:

// browser reset
@mixin margin-reset {
  margin: 0;
}

p {
  @include margin-reset;
  // we don't need a comment here
  // the naming is self-explanatory
}

But, once we do take out the default style, we should make a note that we did in case we change it again and forget—

p {
  // replaces browser reset @include margin-reset (margin: 0;)
  margin: 0 0 1.5rem; 

  // non-reset-y code: 
}

This seems like a lot of work, but you only ever have to do it once, and then you have a full set of code and notes about what is being reset and normalized and why and you’re ready to start on your—efficient, clear, and performant—next project with your default stylesheet ready. I’m still working through how this will look in a full stylesheet, and will update this post with a repo once I’m finished with something, but I think this is an approach that will be more sustainable and straightforward for my code.