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:
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.
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;
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; }
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:
*, *::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.
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; }
<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%; }
Of course, there are many other inline elements, but they’re either uncommon, or making them block-level might be a little presumptuous.
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; }
Annoyingly, they also have some padding on the left side, which you’ll want to remove inherently:
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:
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, becauselist-style: none;
disables this ability in some web browsers.
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; }
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 */ }
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 */ }
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; } }
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; }
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:
label, button, select, summary, [type=radio], [type=submit], [type=checkbox] { cursor: pointer; }
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!
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 is like a DVR for web and mobile apps, recording everything that happens in your web app, mobile app, or website. 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 and mobile apps — start monitoring for free.
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowBuild scalable admin dashboards with Filament and Laravel using Form Builder, Notifications, and Actions for clean, interactive panels.
Break down the parts of a URL and explore APIs for working with them in JavaScript, parsing them, building query strings, checking their validity, etc.
In this guide, explore lazy loading and error loading as two techniques for fetching data in React apps.
Deno is a popular JavaScript runtime, and it recently launched version 2.0 with several new features, bug fixes, and improvements […]
2 Replies to "What should a modern CSS boilerplate look like?"
Nice article. I would love to see the layout grid from this perspective. I would be nice addition to this article.
I totally disagree with most of your ideas.
Changing the box-model is something I really wouldn’t do. I get that it can be a bit of a challenge to get your head around the standard box model at first, but once you get it has at least as many pros as it has cons, and as it is the standard way, it makes your CSS a lot more understandable for others that might have to work with it at some point.
Valuing your own design over the needs of users, as you do with disabling borders and even worse outlines, is plainly rude and selfish. (That you try to be clever about t it by using focus-visible shows, that you are not totally ignorant. It might make it even worse though, as a lack of knowledge might be an excuse for a lack of wisdom…)
You can do this for each element you actually carefully design and where you do offer good accessibility alternatives – I do it all the time. But you can never know if your client or her nephew at some point in the future will install a plugin that shows crucial elements that you made inaccessible with your laziness.
Changin the root font-size because you want to make it easier to calculate things, that most likely should be handled totally differently (ever heard of CSS custom properties?) seem just foolish. Also, code examples with unitless values for font size or with? Really?
Even if some of the standards of the web are disputable, your ‘solutions’ for the most part are worse.