CSS Boilerplate
A default CSS structure for projects of any size.
You may have some questions: Proceed to the FAQ.
/* Establish the order of layers upfront */
@layer core, third-party, components, utility;
@layer core.reset, core.tokens, core.base;
@layer third-party.imports, third-party.overrides;
@layer components.base, components.variations;
/* Reset, normalize, etc. */
@import url('uaplus.css') layer(core.reset);
/* Utility and helper classes */
@layer utility {
.visually-hidden {
clip: rect(0 0 0 0);
clip-path: inset(50%);
height: 1px;
overflow: hidden;
position: absolute;
white-space: nowrap;
width: 1px;
}
}
Frequently Asked Questions (FAQ)
- What's a CSS boilerplate?
- Can you break it down for me?
- How do I use it?
- Do I have to use all layers?
- Isn't @import bad for performance?
- There are !important styles in a third-party style sheet and I can't override them. Why?
- Does this mean I don't need !important anymore?
- Can I use a different reset stylesheet?
- Does it scale?
- Are there other similar boilerplates?
- Where can I learn about cascade layers?
What's a CSS boilerplate?
Like an HTML boilerplate, a CSS boilerplate is one or more CSS files you want to include in every project. For most people, this is just a reset style sheet; for others, it also contains additional base or even component styles.
We take it a step further. This boilerplate uses a reset stylesheet and comes with pre-defined rules, but primarily, it establishes a structure using Cascade Layers, giving you more control over the cascade.
Can you break it down for me?
The boilerplate consists of four main layers.
The core layer is for global custom properties and base styles. Global custom properties are values you want to share across the entire site. Base styles are standard styles you apply to all or most HTML elements for the site.
/* Reset, normalize, etc. */
@import url('uaplus.css') layer(core.reset);
/* Design tokens and base styles */
@layer core {
@layer tokens {
html {
--color-light: oklch(0.97 0.01 0);
--color-dark: oklch(0.22 0.01 0);
}
}
@layer base {
body {
background-color: var(--color-light);
color: var(--color-dark);
font-family: Helvetica, sans-serif;
}
p {
margin-block: 0 1em;
}
}
}
Please note that you don't have to put the global custom properties into a dedicated layer. You don't even have to put the base styles into a dedicated layer. They override layered styles within the core layer if they're unlayered. We're only doing it because it looks more organized and to have more control if the core layer becomes more complex.
The third-party layer overwrites the base layer and contains third-party styles. We import into the imports
sub-layer so that you can use the overrides
sublayer or unlayered styles if you need to override something.
@import url('bootstrap.css') layer(third-party.imports);
@import url('prism.css') layer(third-party.imports);
The components layer contains most of the site's CSS—rules for your teasers, navigations, custom widgets, etc.
@layer components {
.teaser {
…
}
.teaser-large {
…
}
}
You can nest rules in more layers to have more control over variations and different states of your components.
@layer components {
@layer base {
.teaser {
…
}
}
@layer variations {
.teaser-large {
…
}
}
}
Sometimes, we don't have the time to implement a feature properly, but we still need to add code to fix a bug. If you use sub-layers inside your components layer, you put your hotfixes directly in the components layer. These unlayered styles have a higher specificity than the layered styles, which you want in this case, and you can distinguish them from properly implemented code.
Everything that's unlayered inside the components layer must be cleaned up at some point. We also encourage you to always add comments to unlayered styles.
@layer components {
@layer base {
.teaser {
…
}
}
@layer variations {
.teaser-large {
…
}
}
/* Hotfix: Fixes a bug described in issue #1312 */
.teaser {
…
}
}
The utility layer is reserved for utility and helper classes. It comes late in the order of layers because utility classes should have the highest specificity of all regular styles.
/* Utility classes */
@layer utility {
.visually-hidden {
clip: rect(0 0 0 0);
clip-path: inset(50%);
height: 1px;
overflow: hidden;
position: absolute;
white-space: nowrap;
width: 1px;
}
.mbe0 {
margin-block-end: 0;
}
}
How do I use it?
The absolute minimum you need is the first line that establishes the order of the layers. It should be the first line in the first CSS file you include.
@layer core, third-party, components, utility;
Then, you can use as many predefined layers as you want, wherever and how often you want.
Here are two examples:
Basic structure
This basic example uses a single file and two layers. This can be enough for smaller projects.
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My Website</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
</body>
</html>
style.css
@layer core, third-party, components, utility;
@layer core {
html {
--color-light: oklch(0.97 0.01 0);
--color-dark: oklch(0.22 0.01 0);
}
body {
background-color: var(--color-light);
color: var(--color-dark);
font-family: Helvetica, sans-serif;
}
}
@layer components {
.teaser {
…
}
.teaser-large {
…
}
.hero-image {
}
}
Complex structure
In larger projects you probably want to split up your styles into multiple files.
The index.css file establishes the order and imports other style sheets.
index.css
@layer core, third-party, components, utility;
@layer core.reset, core.tokens, core.base;
@layer third-party.imports, third-party.overrides;
@layer components.base, components.variations;
/* UA+ */
@import url('third-party/uaplus.css') layer(core.reset);
/* Third-party styles */
@import url('third-party/prism.css') layer(third-party.imports);
/* Utility and helper classes */
@import url('helpers.css') layer(utility);
@import url('utility.css') layer(utility);
/* Component styles */
@import url('components/teaser.css') layer(components);
@import url('components/hero-image.css') layer(components);
Alternatively, you can also put this in a <style>
tag in the <head>
of your pages.
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My Website</title>
<style>
@layer core, third-party, components, utility;
@layer core.reset, core.tokens, core.base;
@layer third-party.imports, third-party.overrides;
@layer components.base, components.variations;
/* UA+ */
@import url('/assets/css/third-party/uaplus.css') layer(core.reset);
/* Third-party styles */
@import url('/assets/css/third-party/prism.css') layer(third-party.imports);
/* Utility and helper classes */
@import url('/assets/css/utility/helpers.css') layer(utility);
@import url('/assets/css/utility/utility.css') layer(utility);
/* Component styles */
@import url('/assets/css/components/teaser.css') layer(components);
@import url('/assets/css/components/hero-image.css') layer(components);
</style>
</head>
<body>
</body>
</html>
helpers.css
.visually-hidden {
clip: rect(0 0 0 0);
clip-path: inset(50%);
height: 1px;
overflow: hidden;
position: absolute;
white-space: nowrap;
width: 1px;
}
utility.css
.mbe0 {
margin-block-end: 0;
}
components/teaser.css
.teaser {
…
}
components/hero-image.css
.hero-image {
…
}
Do I have to use all layers?
No, you can use as much and as little as you want. You can also add additional layers if you wish.
@layer components {
@layer base {
.teaser {
…
}
}
@layer states {
.teaser[hidden] {
…
}
.teaser.collapsed {
}
}
@layer theme {
.teaser-dark {
…
}
}
}
Isn't @import
bad for performance?
Steve Souders explained why we shouldn't use @import
in 2009. The browser landscape and quality of browser optimization have changed a lot since then. Chris Ferdinandi and Erwin Hofman have done some tests recently, and they both found that importing is still slower than linking files, but their conclusions were very different. For Chris, 300ms extra on a slow 3G connection isn't necessarily a deal breaker; for Erwin, it is.
So, I don't know how critical the impact really is today. All I know is that browsers all agreed to improve @import
performance in the document head when they released layers.
I'll eventually do my own testing and share my findings here.
There are !important styles in a third-party style sheet and I can't override them. Why?
For normal rules, the order of appearance of cascade layers matters. Later-defined layers overwrite earlier-defined layers.
@layer core {
body {
color: red;
}
}
@layer components {
body {
color: blue;
}
}
/* Result: The color is blue */
For !important
rules, it's reversed. That's why it's impossible to overwrite an important rule in a reset or thiry-party style sheet from within another layer.
@layer core {
body {
color: red !important;
}
}
@layer components {
body {
color: blue !important;
}
}
/* Result: The color is red */
The idea behind this behavior is that important styles from any layer will protect them from future layers.
Here's the order of importance for all layers to illustrate how that manifests in this boilerplate. It goes from most important (highest specificity) to least important (lowest specificity).
-
core !important
- core.reset !important
- core.tokens !important
- core.base !important
-
third-party !important
- third-party.imports !important
- third-party.overrides !important
-
components !important
- components.base !important
- components.variations !important
- utility !important
- utility normal
-
components normal
- components.variations normal
- components.base normal
-
third-party normal
- third-party.overrides normal
- third-party.imports normal
-
core normal
- core.base normal
- core.tokens normal
- core.reset normal
Play with the Cascade Layers and !important CodePen to see this effect in action.
Does this mean I don't need !important
anymore?
Yes. In most cases, using !important
is a bad practice because it adds complexity and vulnerability to your style sheets. With cascade layers, it gets even more complicated. We recommend against using !important
, but leverage the power of sub-layers and unlayered styles.
index.css
@import url('prism.css') layer(third-party.imports);
@layer third-party.overrides {
.language-css .token {
color: red;
}
}
If there are important styles in a third-party style sheet that you need to overwrite, you can do that by adding a layer before your imports.
@layer core, third-party, components, utility;
@layer core.reset, core.tokens, core.base;
@layer third-party.very-important, third-party.imports, third-party.overrides;
@layer components.base, components.variations;
@import url('prism.css') layer(third-party.imports);
@layer third-party.very-important {
.language-css .token {
color: red !important;
}
}
Can I use a different reset stylesheet?
Yes, absolutely.
Does it scale?
Uuhm,…I don't know yet, but it should. This site is the first to use this boilerplate, but I plan to use it on more sites later this year. I'll list them here.
Are there other similar boilerplates?
Not that I know, but there are blog posts:
- A Whole Cascade of Layers by Miriam Suzanne
- New Year, New Site by Adam Argyle
Where can I learn about cascade layers?
- Cascade Layers Guide by Miriam Suzanne on CSS-Tricks
- The CSS Cascade, a deep dive (YouTube) by Bramus Van Damme at CSS Day 2022
- Hello, CSS Cascade Layers by Ahmad Shadeed
- Cascade Layers are useless* by Manuel Matuzović