A practical guide to crafting better CSS. Highly opinionated.
- How to enforce visual consistency, i.e. a visual grammar made of visual patterns?
- How no to repeat oneself?
- How to ensure component's CSS code isolation/avoid leakages?
- How to minimize coupling between structure (think
div
) and style?
For sure, there will be trade-offs.
- Define styles in a dedicated stylesheet
- Do not refer to elements
- Do not refer to IDs
- Separate layout and skin
- Separate container and content
- Do not use location-dependent styles
- Create small objects
- Use a CSS framework
- Create components with your favorite library/framework
- The Block__Element--modifier naming convention
- Should I use a preprocessor after all?
- What should I do now?
- Further reading
<!-- HTML -->
<p style="color: red;">Lorem ipsum dolor sit amet.</p>
<!-- HTML -->
<p class="danger">Lorem ipsum dolor sit amet.</p>
/* CSS */
.danger {
color: red;
}
High coupling. In the first place, CSS was designed to decouple structure and style. But in fact, that's a matter of choice (avoiding coupling is hard, so some developers prefer not even try to avoid it, hence CSS-in-JS).
<!-- HTML -->
<p>Lorem ipsum dolor sit amet.</p>
/* CSS */
p {
color: red;
}
<!-- HTML -->
<p class="danger">Lorem ipsum dolor sit amet.</p>
/* CSS */
.danger {
color: red;
}
High coupling. Separating structure and skin allows to:
- apply different styles to a given element, or no style at all
- apply the same style to other elements
- use another more suited element without breaking the style
<!-- HTML -->
<p id="danger">Lorem ipsum dolor sit amet.</p>
/* CSS */
#danger {
color: red;
}
<!-- HTML -->
<p class="danger">Lorem ipsum dolor sit amet.</p>
/* CSS */
.danger {
color: red;
}
Overly specific. There's simply no reason to use IDs. ID selectors introduce an unnecessarily high level of specificity and they are not reusable. IDs are primary used for anchoring (see: Linking to an element on the same page).
<!-- HTML -->
<div class="danger">Lorem ipsum dolor sit amet.</div>
/* CSS */
.danger {
position: fixed;
top: 0;
left: 0;
width: 100vw;
box-sizing: border-box;
padding: 1rem;
background-color: red;
text-align: center;
color: white;
font-weight: bold;
}
<!-- HTML -->
<div class="danger-layout danger">Lorem ipsum dolor sit amet.</div>
/* CSS */
.danger-layout {
position: fixed;
top: 0;
left: 0;
width: 100vw;
}
.danger {
box-sizing: border-box;
padding: 1rem;
background-color: red;
text-align: center;
color: white;
font-weight: bold;
}
Layout refers to properties that determine the size and the positionning of an element, and consequently the overall aspect of the page. Skin refers to properties relative to colors, fonts, text alignment and so on. Skin is much more likely than layout to be reused.
In fact, the previous code could (should?) be refactored!
<!-- HTML -->
<div class="danger-layout danger">Lorem ipsum dolor sit amet.</div>
/* CSS */
.danger-layout {
position: fixed;
top: 0;
left: 0;
width: 100vw;
}
.danger {
box-sizing: border-box;
padding: 1rem;
background-color: red;
text-align: center;
color: white;
font-weight: bold;
}
<!-- HTML -->
<div class="container">
<div class="danger">Lorem ipsum dolor sit amet.</div>
</div>
/* CSS */
.container {
position: fixed;
top: 0;
left: 0;
width: 100vw;
}
.danger {
box-sizing: border-box;
padding: 1rem;
background-color: red;
text-align: center;
color: white;
font-weight: bold;
}
An element should not be responsible for its own positionning!
<!-- HTML -->
<div class="container">
<p class="content">Lorem ipsum dolor sit amet.</p>
</div>
<p class="content">Lorem ipsum dolor sit amet.</p>
/* CSS */
.container .content {
color: red;
}
Or:
// SASS
.container {
& .content {
color: red;
}
}
<!-- HTML -->
<div class="container">
<p class="content danger">Lorem ipsum dolor sit amet.</p>
</div>
<p class="content">Lorem ipsum dolor sit amet.</p>
/* CSS */
.danger {
color: red;
}
An object should look the same no matter where you put it. Otherwise, there is a coupling between structure (.html
) and style (.css
). As a consequence, you should avoid combinators and nesting with preprocessors.
Oh, by the way, are you sure you need a preprocessor?
<!-- HTML -->
<div class="top-fixed-container content-horizontally-centered">
<div class="danger standard-padding">Lorem ipsum dolor sit amet.</div>
</div>
/* CSS */
.top-fixed-container {
position: fixed;
top: 0;
left: 0;
width: 100vw;
}
.content-horizontally-centered {
display: flex;
justify-content: center;
}
.danger {
background-color: red;
color: white;
font-weight: bold;
}
.standard-padding {
box-sizing: border-box;
padding: 1rem;
}
Doing so, you define a visual grammar of styles once and for all, in an index.css
or common.css
file for instance. If you do that in a large extent, remember that that's basically what CSS frameworks do and jump to the next paragraph.
Ok, top-fixed-container
, content-horizontally-centered
and standard-padding
have finally took place in our .html
, as soldiers with the Trojan horse, but we knew that some compromises had to be made. Here, we agree on not separating concerns (structure and style) in favor of a reduced coupling.
The object's granularity is up to you. We usually refer to an extreme granularity as "Atomic CSS", which leads to writing such things:
.background-blue {
background-color: blue;
}
Doing so, is it still usefull to define CSS classes? Should you prefer a red backgroud, then the naming should be adapted accordingly. Which basically means defining and refering to a new class. Heading in this direction, why not simply write CSS in HTML or CSS in JS?
We strongly discourage this approach because it stands down on all the ambitions we had.
The choice is yours, between Bootstrap and Material Design for the main ones, as well as Pure CSS, Bulma or Tailwind for emerging ones. See 9 Best CSS Frameworks in 2021 for their respective pros and cons.
<!-- HTML -->
/* CSS */
<!-- HTML -->
/* CSS */
As the React documentation suggests, if you find yourself always the same and same styles, maybe you should create a component for that.
For those components where you need to write some specific CSS code, BEM is your friend. In essence, it's a useful naming convention and a simple way to avoid leakages. That's what we're going to use it for, although some people use it as a broader methodology.
Please refer to the BEM documentation.
We've seen before that nesting should be avoided as much as possible and that small objects are a valid alternative to preprocessors when it comes to creating a visual vocabulary.
This makes preprocessors (LESS, SASS) much less usefull, though maybe not useless in some very specific cases.
TODO