DRY CSS is a lie

Last week I had a discussion with a colleague of mine about different ways to share styles in CSS. I’ve already written about how to write maintainable CSS, but since this is still quite controversial for many I’d like to elaborate my point from a different perspective.

#How to make things look the same

Let’s say the design you implement uses rounded boxes as a visual element.

The code requirements are simple:

#Just using classes doesn’t work

The first solution one might come up with is creating a class.

It might be called .box. Depending on how you structure your views, this class then is used by multiple templates or components, that are supposed to look like this box.

.box {
  border-radius: 5px;
  padding: 10px;
  background: white;

At some point somebody might want to use .box on a form, which needs more space. So .form gets updated to overwrite the padding defined in .box.

The markup might look like this:

<form class="form box"></form>

🚨 Warning! This change broke the requirements.

By overwriting styles, you’ve implicitly created a new variation of .box. This variation is coupled to .form, which leads to the following problems:

Using more than one class on an HTML element strongly couples these classes together.

Updating any of them could always unintentionally affect the other. Looking at the CSS class declaration doesn’t tell you, which classes and which HTML it depends on.

#mixins, extend, composes don’t work

Sharing styles with a preprocessor is not much different to using multiple classes on the same element.

.form {
  @extend .box;
  padding: 0; // overwrites .box styles

It only makes the implicit dependency between .form and .box a little bit more explicit in the CSS file.

One can now see that .form depends on .box, but only if you look at the definition of .form. Looking at the .boxclass won’t tell you that it looks different when used on a form.

It also still breaks the requirements in the same way as multiple classes, because it is still overwriting styles.

It doesn’t matter if the inherited styles are coming from a class or a mixin. The only difference is that mixins cannot be used directly in HTML.

composes is CSS components way of overwriting styles and has the same disadvantages. It only compiles to a different CSS output.

#OOCSS/Functional CSS does not work

The great thing about OOCSS is, that it taught us how to create variations of a visual element by using modifiers. Instead of overwriting a class one creates a new modifier class.

<form class="box box--more-space form"></form>

Instead of creating a variation of .box by having .form overwrite it, we’ve created a new class, which explicitly is a variation of .box.

Since .form and .box are still used on the same HTML element, this does not decouple them. .form could still overwrite .box or its variations.

The OOCSS naming convention cannot give you any guarantees that this won’t happen!

“Functional CSS“ is not much different to OOCSS. It basically says: treat classes as immutable. This means never overwrite styles of another class. (unless the framework also has something like base classes)

That sounds great in theory, but again does nothing to actually add any encapsulation. Hence it also cannot give you any guarantees.

#Why do these solutions not work?

The first mistake we made is trying to decouple CSS from HTML, although we always need both to paint something on the screen.

The minimum level of abstraction for a visual element on the web is a combination of CSS and HTML.

If visual elements consist of CSS and HTML, you as an author get one step closer to:

Some people try to do the same by putting HTML as comments in CSS, but documentation is no guarantee.

The second mistake is that, none of the solutions tried to add encapsulation. Anyone can overwrite any property of any class.

As an author of a class, there is no way in CSS for you to say: All elements using this class should always look like this. Nobody can change these properties.

If we want to make things look the same, we need to protect them from looking different.

These two mistakes are the reason why DRY CSS is a lie. DRY CSS means writing fewer lines of CSS, but with the same result as copy-pasting. CSS alone without HTML and without encapsulation is not able to consistently make things look the same.

#How to safely share styles

Acknowledging our mistakes leaves us with two rules, we need to follow to safely make things look the same:

  1. Use a combination of CSS and HTML and maybe JS.
  2. Write CSS that only affects the given HTML.

The combination of CSS + HTML (+ JS) is called a component.

If you follow these rules you’ll get encapsulation and therefore the following benefits:

A colleague of mine at diesdas wrote and open-sourced a linter for these rules. This reduces the time spent reviewing CSS in pull requests and decreases the chance of missing files where they are not followed.