CSS can fool you into thinking that it's easy to write.
However, it's not the code that's tricky, it's getting the formatting and organisation right. Without it, you can quickly find yourself with code that is too complicated to read, maintain and debug.
I've put together a non-exhaustive list of best practices and rules that should help you create and maintain a clean and extensible CSS style sheet.
1 - Segment CSS into multiple files
It is preferable to separate your CSS code into several files each corresponding to a module, view or page in your application.
Choose your preference and implement it consistently. Organising your CSS into files makes it easier to navigate.
For example, in WordPress, it might be a good idea to split your code by content template.
- page.css
- single.css
- header.css
- footer.css
- archive.css
- template-custompage.css
Breaking your CSS up into multiple files is especially beneficial if you use a CSS preprocessor (Sass or Less).
2 - Establish a naming convention
"There are only two hard problems in Computer Science: cache invalidation and naming things" Phil Karlton
Although naming things is hard, for sake of maintainable code, it is essential that your elements adopt a consistent naming convention.
If in your application there is a block which contains a secondary navigation, you can give it a class which will be called .secondary-nav
or .nav-secondary
or .navSecondary
or .Nav_Secondary
.
I choose to follow the BEM methodology.
BEM stands for Block, Element, Modifier, and is a naming methodology that attempts to divide your page into small reusable components.
To put it simply, a page can contain several blocks, each of these blocks can contain one or more elements which are specific to it.
Block
Blocks represent a block of design, typically key components on your page.
Let's take the example of a web page from a blog. Being made up of 4 blocks:
- Navbar
- Main content
- Side-bar
- Footer
We write the corresponding CSS class names quite simply as:
.nav-bar { }
.side-bar { }
.main-content { }
.footer { }
Note: We use Hyphen Delimited Strings to name things
.example-class
. Camelcasing, unlike JavaScript, isn’t well-suited to CSS. Using a hyphen in CSS is arguably more readable and consistent with CSS property names.
Elements
Each block of our theoretical web page is made up of different elements, for example the side-bar might contain a:
- Title
- Latest articles
- A list of latest comments
According to the BEM naming convention, element class names are derived by adding two underscores, followed by the element name.
.side-bar__latest-articles { }
Modifiers
Modifiers are a flag on an object used to change the behavior or appearance via a CSS class.
The "latest articles" element can have several Modifiers. For example:
- Hidden
- Visible
- Active
- disabled
Modifier class names are derived by adding two hyphens followed by the element name.
.side-bar__latest-articles-- Active
{
/* ... styles */
}
Basically, this how the BEM naming convention works!
3 - Use Object Oriented CSS (OOCSS)
Object-Oriented CSS (OOCSS) draws upon many concepts in object oriented programming such as the single responsibility principle and separation of concerns.
The aim of OOCSS is to produce components that are flexible, modular and interchangeable.
Take the example of a red and green button:
.button-red
{
display: block;
font-size: 12px;
color: red;
border: 1px solid red;
}
.button-green
{
display: block;
font-size: 12px;
color: green;
border: 1px solid green;
}
In these two examples, there is a repetition of code.
We will instead try to group the properties that these buttons share and then create an additional class corresponding to their respective differences.
.button
{
display: block;
font-size: 14px;
border-width: 1px;
border-style: solid;
border-color: transparent;
}
.bouton-red
{
border-color: red;
color: red;
}
.bouton-green
{
border-color: green;
color: green;
}
In HTML, the red button will be styled like so:
The goal is to keep a basic consistency for all the button elements and overload them when necessary with an additional class that extends their properties.
4 - Understand the Open / Close Principle (OCP)
This principle goes hand in hand with the example discussed in the previous point.
The open–closed principle states "software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification".
The Open/Closed Principle, OCP in short, is credited to Bertrand Mayer, a French programmer, who first published it in his book Object-Oriented Software Construction (1988).
In other words, classes should never be modified from a different place from where they are declared. We should instead use several classes to achieve the desired behavior.
Look at the following example of badly written code :
.button {
/* ... button's styles */
color: red;
}
. panel {
/* ... panel's styles */
}
. panel .button {
color: grey;
margin-left: 18px;
}
There are multiple offenses in this code that break the OCP:
- We modified the color of the button when inside a panel.
- We modified the
.button
class by adding a property
- We added to the margin to the button
Instead, we could have done:
.button {
/* ... button's styles */
color: red;
}
.button--grey {
color: grey;
}
.panel {
/* ... panel's styles */
}
. panel__action {
margin-left: 18px;
}
In the improved code, we have extended the .button class with a .button--grey
option, in other words a modifier (to follow the BEM naming convention).
This modifier allow us to use the grey variation anywhere we see fit in the code.
Additionally, instead of adding the margin directly to the .button
through an override, we've now created a class attached to the .panel
class called .panel__action
. This new class is responsible for creating the required white space.
In the real world, CSS classes like .button
and .panel
are being instantiated in different places (even different files).
This approach has multiple benefits:
- By not modifying button at all from panel, we keep the components decoupled from one another. Before this, if we were to delete the button, it would have messed up all our panel margins.
- We are not tied to structure (using a button with .panel__action, it could, for example, be a link.
- It's safer and more flexible, because for instance, if for a specific use-case we need to add another button into the panel, we might not want it to implement a margin-left property.
5 - Avoid over qualified selectors
One basic way to make your CSS much nicer to work is to avoid (over) qualified selectors and keep specificity low.
That is to say, it's better to write .nav{}
than ul.nav{}
.
This is mainly because you can use .nav
on anything at all, be a ul
or an ol
. Prefix an element to a class selector increases specificity unnecessarily.
If you're not careful, unnecessary specificity can really get your project into a mess.
If you still need to express specificity in your stylesheet in order to communicate to other developers where an element belongs, some developers adopt the quasi-qualified selector hack. This is where you comment out the parent element, so it still reads properly but without altering it's specificity.
/*ul*/.nav{
/* ... styles */
}
6 - Be DRY
DRY, (Don't Repeat Yourself), is a principle that aims to reduce the amount of code written and to group duplicate declarations.
You should apply it if two declarations very often coexist together. The DRY principle can be very useful when coding in Less or Sass.
Example:
.error-text
{
font-size: 14px;
background-color: #ccc;
border-color: red;
color: red;
}
.block-error
{
width: 200px;
height: 200px;
background-color: #000;
font-size: 17px;
border-color: red;
color: red;
}
We can see that the two classes share the border-color
and color
properties. Presumably they are always found together for a certain number of classes.
So we can create a mixin
to help avoid repetitive code. Mixins allow you to define styles that can be re-used throughout your stylesheet.
They're created like so:
@mixin base-error ()
{
border-color: red;
color: red;
}
And used like so:
.error-text
{
font-size: 15px;
background-color: #ccc;
include base-error ();
}
.block-error
{
width: 250px;
height: 250px;
background-color: #000;
font-size: 17px;
include base-error ();
}
Conclusion
This article has outlined a set of best practices that lay a solid foundation for writing readable and extensible CSS code.
Sometimes, it just isn't possible to implement these rules in their purity.
For example when it is necessary to style a WordPress theme and a plugin like WooCommerce, the established CSS naming conventions are not necessarily consistent or applicable to your way of doing things. On those occasions, it's a question of compromise.
About the Author