CSS was meant to style academic documents and simple sites (eg. wiki, blogs) where the cascade and descendant selectors makes a lot of sense. Unfortunately many sites we build nowadays are way more complex than that, and what used to work on simple projects doesn’t scale very well. We need to find smarter ways to code CSS to avoid the common issues and re-think the way we do our work. We should learn from the experience of other devs working in different domains, and apply into our own domain. Things like separation of concerns, modularity, encapsulation, DRY can (and should) be applied to large scale CSS projects as well. The main problem is that most people who are good at CSS doesn’t necessarily have a Computer Science background, most of them started as designers and learned CSS by themselves (that was my case…). That’s the main reason why I’m writing this post.
I won’t get into too much details/examples but will try to explain briefly each concept. Some of the SOLID principles are open to multiple interpretations on the CSS context - since we are shoehorning the concepts - that’s one of the reasons why I decided to not give detailed examples. I also think we should understand the idea and not be tied to a specific implementation.
What is a large CSS project?
The notion of a large project varies a lot, I used to think that a project with 2000+ lines of CSS was big but nowadays most of my projects have more than 4000 lines of CSS and I don’t think they are that big. Organization and the structure of the project can make a huge difference in the “perceived size” of an application. But for the sake of brevity let’s just consider that anything that requires more than 4K lines of well-written CSS is a non-trivial application/site. When a project is under 1-2K LOC it doesn’t really matter how chaotic things are, you can still wrap your head around it. Beware that big messes usually starts small and that it’s easier to start with a good structure than to change it later.
The benefits of the techniques below will only be perceived after your app reaches a certain size and will increase exponentially as the project grows. The benefits will also vary a lot based on the kind of project, be pragmatic about it, try to understand the reasoning behind it and think if it does apply to your project. - I don’t follow these rules on all projects and you certainly shouldn’t as well.
SOLID is an acronym for five basic principles of object-oriented programming and design that when applied together can make systems easier to maintain and to extend over time. The term was coined by Uncle Bob and I’m pretty sure he didn’t had CSS in mind (although I didn’t asked him).
- Single Responsibility Principle
- Open/Closed Principle
- Liskov Substitution Principle
- Interface Segregation Principle
- Dependency Inversion Principle
Single Responsibility Principle
This principle states that an object should have only a single responsibility, and that responsibility should be entirely encapsulated by the object. When applied to CSS this means that we should separate structure from presentation, instead of creating a single class and/or element that does both, we should decouple them so you can change the skinning of the components without affecting the structure.
One way of doing that is to delegate the dimensions of the element to a grid system (adding multiple classes to the element or extending a base class if using a preprocessor) or set the size on a parent element.
CSS rules should have a high cohesion, you shouldn’t normally see too many different kinds of rules grouped together. You shouldn’t have text styles + borders + colors grouped with dimensions + positioning and floats.
By doing so we can reuse the same modules inside a different container and increase the chance of reuse. It will also make maintenance easier since you can change the skinning without affecting the structure and can toggle the visual easily based on the context (eg. element with same structure but different colors).
Another good tip is to break your code into multiple files - each file is responsible for a single responsibility - that way it’s easier to work on a team (less conflicts) and your CSS will also be easier to understand since everything is grouped by context.
“software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification”
That means that you should make it easy to overwrite/extend the base rules and should avoid editing the base classes.
You should only edit a base class if you are fixing an error, never add new behavior, otherwise you might introduce conflicts. That means that you should not change your
reset.css file or edit rules that affects multiple elements after the project is stable. Create a new class that augments it instead of editing it.
In many projects I avoid setting base styles to tags as much as possible, specially when the design doesn’t follow a clear semantic structure – when for instance we might need 20+ font styles and the same elements might have different spacing/color/weight depending on the context. If you reduce the amount of rules that affects global elements you will also reduce the chance of breaking the open/closed principle.
Another main advantage of not having base styles is that you won’t need to overwrite the defaults as much. (but you might need to set more styles).
Liskov Substitution Principle
“objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program”
@extend others shouldn’t have a different behavior than the base class. You should be able to toggle between them without affecting the correctness of the app.
The idea behind this principle is that if you can swap Parent classes with their Children, without causing any undesired side effects, you will have a system that is more flexible and easier to understand. It will increase code reuse on cases that you couldn’t predict when the project started.
That means, modifiers should ideally affect only things that wouldn’t break the layout. If the base class doesn’t have a fixed height the child class shouldn’t have as well, the display and position also shouldn’t be changed.
Remember that composition is usually preferred over inheritance.
PS: I don’t follow this principle by heart on my CSS, specially since I use modifiers more as a Decorator than a subclass. But if you are using a preprocessor on a large project and (ab)using the
@extend feature that is something you should care about.
Interface Segregation Principle
“many client specific interfaces are better than one general purpose interface.”
That means, it’s better to have multiple specific base modules than to have a single generic one. It will increase the cohesion of your modules and make code easier to refactor/change/maintain (since it reduces tight coupling).
Sometimes it can be really tempting to create a base module that fits multiple scenarios trying to increase code reuse as much as possible. To be honest, lately I’ve been thinking that not all code reuse is a good thing. If you are doing it just for the sake of reusing (specially CSS) you might be increasing the coupling between modules without a need, that will make further changes harder to implement since it will cause a ripple effect to undesired modules (eg. changing the margin around an article in the homepage would also affect the margin around a blog post on the inner pages).
This problem might be hard to spot and even hard to replicate unless the project requirements change (and they usually do change). If you find yourself redefining many properties every time you need to augment a certain class, your base styles are probably too generic.
Dependency Inversion Principle
- High-level modules should not depend on low-level modules. Both should depend on abstractions.
- Abstractions should not depend upon details. Details should depend upon abstractions.
I like to think this rule on the CSS domain means that the container shouldn’t care about the existence and the visual style of its children as long as it behaves how it expects it to behave. For an
.article component it shouldn’t matter how the
.title is styled, as long as the title doesn’t break the layout. We should delegate the styling of child components to their own modules and do it in a way that the child elements can be swapped without affecting the parent element. This is the core idea behind OOCSS.
Nested selectors are a sign that you might be breaking this rule. Instead of making nested selectors like
#sidebar .group > h3 you should create a new class
.group-title. That way it can be an
<div> or any other element you need. It will be easier to create new modules with slightly different styling/behavior by simply swapping the dependencies (child elements). This also favors composition over inheritance.
Most concepts can be applied into multiple domains, understand the problems they try to solve so you can apply them when appropriate.
“Smart people learn from their mistakes. But the real sharp ones learn from the mistakes of others.” – Brandon Mull
Keep your code organized and you won’t lose momentum. You shouldn’t be afraid of making changes to your CSS, if your modules are loosely coupled they won’t interfere with each other, which means they will be easier to maintain.
If you want to see some code samples and how to apply these concepts check the links below, they get into further details about the problems and solutions.
- Idiomatic CSS
- Our (CSS) Best Practices Are Killing US
- Scalable and Modular CSS
- Keep your CSS selectors short
- One Module or Two
- The Single Responsibility Principle Applied to CSS
- The open/closed principle applied to CSS
- Don’t use IDs in CSS selectors?
- Principles for writing good CSS
This article is also available in Japanese.