What should a modern CSS boilerplate look like?
Tags: css, webdev
Published: 2022-12-01 16:04:26

**Written by [Daniel Schwarz](https://blog.logrocket.com/author/danielschwarz/)✏️**

A CSS boilerplate is a set of unoriginal and unspecific CSS rules designed for inherent use in web projects. Using one can help you to get your CSS up and running in seconds. 

You can find CSS boilerplate templates online, or you can create and maintain your own one, to make `1rem = 10px`, reset various CSS properties, enforce your preferences on raw HTML elements, and so on. I’m certain that there are many CSS rules that you habitually include in all of your projects (some of which might do things that you believe CSS should do out of the box), so even if you don’t use a CSS boilerplate consciously, you almost certainly have the makings of one. 

The problem with using popular CSS boilerplates is that they tend to be overzealous, making way too many assumptions about what the average website needs and resetting every CSS property known to exist. In fact, it wouldn’t be totally inaccurate to refer to some of them as bloatware. That being said, many custom CSS boilerplates are more extravagant than they need to be, contain a lot of redundant code, and become larger over time without much thought. 

In this article, I’ll show you what I believe are the must-haves of a modern CSS boilerplate. The result? Hopefully, something more minimal, predictable, and easier to maintain going forward. We’ll also discuss some helpful but not essential CSS rules. 

_Jump ahead:_

*   [Don’t reset margins](#dont-reset-margins)
*   [Definitely reset the box model](#reset-box-model)
*   [What you do with borders is up to you](#what-borders)
*   [Force images to behave](#force-images-behave)
*   [Reset (most) lists](#reset-most-lists)
*   [Enhance link underlines](#enhance-link-underlines)
*   [Change the root font size](#change-root-font-size)
*   [Disable the text inflation algorithm](#disable-text-inflation-algorithm)
*   [Enable smooth scrolling](#enable-smooth-scrolling)
*   [Rethink how focus works](#rethink-focus-works)
*   [Provide interactive elements with a cursor](#provide-interactive-elements-cursor)

## Don’t reset margins <a name="dont-reset-margins">

There are quite a few HTML elements that web browsers automatically apply margins to, and although it’s very common to reset them (i.e., `margin: 0;`), you shouldn’t because web browsers do this for legitimate reasons. The `<body>` element uses margins to ensure that webpages remain readable and that touch targets don’t fall to the edges of mobile screens in the event that the CSS doesn’t load. Content elements (e.g. `<h1>`, `<p>`, and `<figure>`) apply margins for the same reason — accessibility. The `<dialog>` element uses margins to center itself in the middle of the screen. 

I’m not saying that we should let web browsers decide everything for us, but since you’ll certainly be defining your own margins according to a design spec (right?), why reset them only to redefine them later? This is how I feel about CSS resets in general, which is why I won’t be talking about them that much.

## Definitely reset the box model <a name="reset-box-model">

Imagine you set the `width` of an element to `700px`, the `border` to `1px`, and the `padding` to `1px` too — what’s the physical width of the element now? Is it still `700px` with the border and padding forming on the inside, or is it now `704px` with the border and padding added on? Most people would agree that if you set the `width` to `700px` then that’s what the physical width should be, but ordinarily, it will, in fact, be `704px`. To understand why you’ll want to read about the [CSS box model](https://blog.logrocket.com/css-margin-vs-padding/). 

To make a selector’s box model act in what most consider a more logical way, you’ll want to reset it with this CSS rule:

```css
box-sizing: border-box;
```

With this CSS rule in place, `700px` means `700px`, and any borders and padding will form on the inside, reducing the content area's width instead of being added onto the outside of the element. 

Although, since there’s no telling what exactly you’ll be adding the borders and padding to, you’ll just want to apply the rule to everything using the universal selector `(*)`:

```css
* {
    box-sizing: border-box;
}
```

Writing `*` is quicker than writing a list of specific selectors to target. Also, the universal selector’s impact on performance is negligible these days and doesn’t impact the specificity weight value. 

However, it doesn’t target [pseudo-elements](https://blog.logrocket.com/css-pseudo-elements-guide/), so considering how common it is to use `::before` and `::after`, you’ll probably prefer the following snippet instead:

```css
*,
*::before,
*::after {
    box-sizing: border-box;
}
```

But why doesn’t the box model work more logically right out of the box? The reason is that the `box-sizing` property is a relatively new one, and having web browsers change the way they render layouts would completely break the layouts that already exist. 

So even though `box-sizing: border-box;` It isn’t the default; I imagine most developers believe it to be the best approach.

## What you do with borders is up to you <a name="what-borders">

`border: none;` is a CSS rule that features in a lot of CSS resets. Buttons, dialogs, form-related elements, and images on Firefox all have borders by default. This is great for usability because borders can increase the affordance of interactive elements, but if you’d rather address affordance differently, use the following code snippet to remove all borders altogether:

```css
* {
    border: none;
}
```

## Force images to behave <a name="force-images-behave">

`<img>` elements are inline, so they’ll often have that annoying little space underneath them. You can fix this by tinkering with `vertical-align`, but there’s no value that works in every scenario, and it’s very unlikely that you’ll want your images to be inline, anyway. You’re better off fixing the issue with `display: block;`. 

I’ve also thrown in `max-width: 100;`, which prevents images from blowing up their containers for absolutely no reason at all. Why? Because containers should contain!

```css
img {
    display: block;
    max-width: 100;
}
```

Of course, there are [many other inline elements](https://developer.mozilla.org/en-US/docs/Web/HTML/Inline_elements), but they’re either uncommon, or making them block-level might be a little presumptuous.

## Reset (most) lists <a name="reset-most-lists">

Whether you’re using ordered lists (`ol`), unordered lists (`ul`), or menus (`<menu>`), it’s unlikely that you’ll want the list markers (i.e., bullets, numbers, etc.,) that come with them since they look rather unsightly on user interfaces. The following snippet removes them, but not from those used in articles where it makes sense to use them in their natural form:

```css
menu:not(article menu),
ol:not(article ol),
ul:not(article ul) {
    list-style: none;
}
```

Annoyingly, they also have some padding on the left side, which you’ll want to remove inherently:

```css
>menu,
ol,
ul {
    padding-left: 0;
}
```

Additionally, since list markers appear on the outside of lists, removing the padding using the snippet above can cause the list markers (the ones used in articles that we’re not resetting) to overlap neighboring content or overflow the viewport, so you’ll want to switch them to the `inside` instead:

```css
article ol,
article ul {
    list-style-position: inside;
}
```

> Tip: Add the `role="list"` attribute-value to all lists that you’d like screen readers to announce as such, because `list-style: none;` disables this ability in some web browsers.

## Enhance link underlines <a name="enhance-link-underlines">

As a designer, I believe that typography is the most important part of a design’s aesthetic. I also believe that buttons are overused, although this makes sense since underlinked links aren’t pleasant to look at. However, there’s now a way to make underlines thicker (to match the size and weight of the font) and spacier. It only requires two CSS properties:

```css
a {
    /* Places underlines below the descenders */
    text-underline-position: under;

    /* Sets the thickness as a percentage of the font size */
    text-decoration-thickness: 8;
}
```

## Change the root font size <a name="change-root-font-size">

If you prefer using `rem` units ([which are better for accessibility](https://blog.logrocket.com/a-developers-guide-to-designing-accessible-websites/)), `1rem` is equal to `16px` (the default root font size). Naturally, it’s much easier to convert `px` to `rem` when `1rem` is equal to `10px`, but to do that, you’ll need to change the root font size to `62.5`, like so:

```css
/* <html> = the root */
html {
  font-size: 62.5; /* (62.5/100) * 16px = 10px */
}
```

## Disable the text inflation algorithm <a name="disable-text-inflation-algorithm">

On smartphones and tablets, web browsers inflate the font sizes of text elements that use 100 percent of the viewport’s width using a text inflation algorithm. This is done with the best intentions (to enhance readability), but it can be quite unpredictable, with some elements being hit by the algorithm and some not. 

Besides, I’m certain you wouldn’t want web browsers deciding your font sizes for you — because I wouldn’t want it either. Any good designer or developer knows that text should be readable on all devices, and invests time and energy into making websites responsive. To disable this algorithm, you’ll want to apply `text-size-adjust: none;` to the root/`<html>`:

```css
html {
  -webkit-text-size-adjust: none; /* for iOS Safari */
  text-size-adjust: none; /* for other mobile browsers */
}
```

## Enable smooth scrolling <a name="enable-smooth-scrolling">

Pretty simple — instead of instantly snapping to same-page anchors and text fragments, `scroll-behavior: smooth;` enables users to scroll to them smoothly. Also, to cater to those that prefer reduced motion for accessibility reasons, the rule is wrapped in a `prefers-reduced-motion: no-preference` media query:

```css
@media (prefers-reduced-motion: no-preference) {
  html {
        scroll-behavior: smooth;
  }
}
```

## Rethink how focus works <a name="rethink-focus-works">

The `:focus-visible` pseudo-class provides a less aggressive approach to styling interactive elements that are currently being focused on. More specifically, using the snippet below, you can use it to remove the outline for mouse users but keep it for keyboard users. _With just one selector, no less!_

```css
:focus:not(:focus-visible) {
    outline: none;
}
```

## Provide interactive elements with a cursor <a name="provide-interactive-elements-cursor">

Many interactive elements, such as buttons, don’t have pointer (hand) cursors when hovered as anchor tags do. I’ve always found this odd, especially for interactive elements without clearly defined regions, such as most labels. The cursor just doesn’t change _at all_, which in my opinion, shows that the spec gets it wrong sometimes. It’s a small caveat that only affects desktop users, but it’s one that I believe is worth fixing. 

The following snippet adds `pointer` cursors to interactive elements that don’t already have an alternative cursor, for example, `text inputs`, `textareas`, and more:

```css
label,
button,
select,
summary,
[type=radio],
[type=submit],
[type=checkbox] {
    cursor: pointer;
}
```

## Closing thoughts

It’s important to think about your CSS boilerplate carefully. After all, it _is_ the foundation of every website you'll work on. This includes reviewing it regularly as well as reviewing any changes you make to it over time. If you’ve ever worked on a design system before (or even just design versioning), you might find the concept of reviewing changes quite familiar, as well as the outcome of changes feeling purposeful and minimal. 

I look forward to hearing about the CSS rules that feature in your CSS boilerplate and whether you plan on making changes to it after reading this article. Drop a comment in the box below!

---

## Is your frontend hogging your users' CPU?

As web frontends get increasingly complex, resource-greedy features demand more and more from the browser. If you’re interested in monitoring and tracking client-side CPU usage, memory usage, and more for all of your users in production, [try LogRocket] (https://logrocket.com/signup/).

[![LogRocket Dashboard Free Trial Banner] (https://blog.logrocket.com/wp-content/uploads/2019/12/cpu-memory-usage.png)](https://logrocket.com/signup/)

[LogRocket] (https://logrocket.com/signup/) is like a DVR for web apps, recording everything that happens in your web app or site. Instead of guessing why problems happen, you can aggregate and report on key frontend performance metrics, replay user sessions along with application state, log network requests, and automatically surface all errors.

Modernize how you debug web apps — [Start monitoring for free] (https://logrocket.com/signup/).