DEV Community

Cover image for What should a modern CSS boilerplate look like?
Matt Angelosanto for LogRocket

Posted on • Originally published at

What should a modern CSS boilerplate look like?

Written by Daniel Schwarz✏️

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

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

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.

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:

box-sizing: border-box;
Enter fullscreen mode Exit fullscreen mode

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 (*):

* {
    box-sizing: border-box;
Enter fullscreen mode Exit fullscreen mode

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, so considering how common it is to use ::before and ::after, you’ll probably prefer the following snippet instead:

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

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

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:

* {
    border: none;
Enter fullscreen mode Exit fullscreen mode

Force images to 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!

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

Of course, there are many other inline elements, but they’re either uncommon, or making them block-level might be a little presumptuous.

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:

menu:not(article menu),
ol:not(article ol),
ul:not(article ul) {
    list-style: none;
Enter fullscreen mode Exit fullscreen mode

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

ul {
    padding-left: 0;
Enter fullscreen mode Exit fullscreen mode

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:

article ol,
article ul {
    list-style-position: inside;
Enter fullscreen mode Exit fullscreen mode

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

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:

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

    /* Sets the thickness as a percentage of the font size */
    text-decoration-thickness: 8;
Enter fullscreen mode Exit fullscreen mode

Change the root font size

If you prefer using rem units (which are better for accessibility), 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:

/* <html> = the root */
html {
  font-size: 62.5; /* (62.5/100) * 16px = 10px */
Enter fullscreen mode Exit fullscreen mode

Disable the 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>:

html {
  -webkit-text-size-adjust: none; /* for iOS Safari */
  text-size-adjust: none; /* for other mobile browsers */
Enter fullscreen mode Exit fullscreen mode

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:

@media (prefers-reduced-motion: no-preference) {
  html {
        scroll-behavior: smooth;
Enter fullscreen mode Exit fullscreen mode

Rethink how 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!

:focus:not(:focus-visible) {
    outline: none;
Enter fullscreen mode Exit fullscreen mode

Provide interactive elements with a 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:

[type=checkbox] {
    cursor: pointer;
Enter fullscreen mode Exit fullscreen mode

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.

LogRocket Dashboard Free Trial Banner

LogRocket 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.

Top comments (2)

alohci profile image
Nicholas Stimpson

You're missing "%" on some values in the article - max-width, font-size & text-decoration-thickness.

fruntend profile image

Сongratulations 🥳! Your article hit the top posts for the week -
Keep it up 👍