Crafting CSS
A practical guide to crafting better CSS. Highly opinionated.
Questions
- 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.
Summary
- 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
Define styles in a dedicated stylesheet
Don't
<!-- HTML -->
<p style="color: red;">Lorem ipsum dolor sit amet.</p>
Do
<!-- HTML -->
<p class="danger">Lorem ipsum dolor sit amet.</p>
/* CSS */
.danger {
color: red;
}
Why
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).
Do not refer to elements
Don't
<!-- HTML -->
<p>Lorem ipsum dolor sit amet.</p>
/* CSS */
p {
color: red;
}
Do
<!-- HTML -->
<p class="danger">Lorem ipsum dolor sit amet.</p>
/* CSS */
.danger {
color: red;
}
Why
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
Do not refer to IDs
Don't
<!-- HTML -->
<p id="danger">Lorem ipsum dolor sit amet.</p>
/* CSS */
#danger {
color: red;
}
Do
<!-- HTML -->
<p class="danger">Lorem ipsum dolor sit amet.</p>
/* CSS */
.danger {
color: red;
}
Why
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).
Separate layout and skin
Don't
<!-- 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;
}
Do
<!-- 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;
}
Why
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.
Separate container and content
In fact, the previous code could (should?) be refactored!
Don't
<!-- 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;
}
Do
<!-- 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;
}
Why
An element should not be responsible for its own positionning!
Do not use location-dependent styles
Don't
<!-- 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;
}
}
Do
<!-- 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;
}
Why
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?
Create small objects
An example, please!
<!-- 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;
}
Why
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.
Limitations
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.
Use a CSS framework
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.
Create components with your favorite library/framework
Don't
<!-- HTML -->
/* CSS */
Do
<!-- HTML -->
/* CSS */
Why
As the React documentation suggests, if you find yourself always the same and same styles, maybe you should create a component for that.
The Block__Element--modifier naming convention
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.
Should I use a preprocessor after all?
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.
What should I do now?
TODO