Apprentice Blog of the Week: Isolate CSS

Apprentice Blog of the Week: Isolate CSS

Christoph Gockel
Christoph Gockel

October 07, 2014

When developing software, it's generally desirable to hide the details. This allows you to increase the cohesion of parts in our software, and to decouple them from each other via well-defined interfaces or boundaries.

This is a common approach when it comes to isolating ourselves from external dependencies in our code. But why only apply it to code we write for the back-end? The same abstraction principles apply no matter what external dependency we want to isolate ourselves from.

Thus, it can and should be applied to any CSS framework we use. Regardless of whether we're using Bootstrap, Pure, Less, Blueprint, 960, or any other framework, our applications should not have any direct coupling to an external framework. We should not use any CSS classes that were not defined by us for our project.

I will use Yahoo!'s Pure framework for the following code examples, but the principles apply for any framework.

Pure provides pure-g classes to create a grid, and several pure-u-* classes as units/columns within these grids.

A grid with two buttons next to each other can be defined like this:

<div class="pure-g">
		<div class="pure-u-1-2">
				<a class="pure-button" href="/">Back</a>
		</div>
		<div class="pure-u-1-2">
				<a class="pure-button" href="/game/restart">Restart</a>
		</div>
</div>

As we can see, this markup contains many references to classes that are defined by Pure. By doing that, we have coupled the view directly to the framework we've chosen.

Directly coupling to a framework is not always a good approach. Developers react with hesitation as soon as they introduce such a tight coupling in a language like Ruby, and the same abstraction and isolation principles should apply to CSS as well. The view belongs to our application, and therefore we should isolate any external dependency from that.

Ideally, we would write the previous code in a way that puts us in charge of what CSS classes the elements use.

An example of that would look something like this:


<div class="grid-container">
		<div class="half-width">
				<a class="button" href="/">Back</a>
		</div>
		<div class="half-width">
				<a class="button" href="/game/restart">Restart</a>
		</div>
</div>

The classes in this example do a better job of expressing what the HTML intends to do.

We use grid-container instead of pure-g, because that's what pure-g really was.

We use half-width instead of pure-u-1-2, because pure-u-1-2 was really just a div that spanned half of the width of its parent element.

And we use button instead of pure-button, because... well, it's just a button.

How can we achieve CSS isolation?

To be able to use the CSS classes in the second example, I used Sass. Sass allows you to “extend” new selectors from existing selectors.

With this feature, we can create new selectors for our views that match the domain of the application we're developing.

The definitions for the classes of the last example look like this:

.grid-container {
		@extend .pure-g;
}

.half-width {
		@extend .pure-u-1-2;
}

.button {
		@extend .pure-button;
}

Sass will create the classes with the appropriate content for us. With these definitions, .grid-container will have the same declarations that .pure-g has.

This is not only possible for single classes, either. Multiple usages of classes can also be merged together.

For example, a button that is coloured in Pure's primary colour is declared like this:

<button type="submit" class="pure-button pure-input-1 pure-button-primary">Play!</button>

Its SCSS declaration looks like this:

button.play-game {
		@extend .pure-button, .pure-input-1, .pure-button-primary;
}

We are now able to declare the button with better expressed intent:

<button type="submit" class="play-game">Play!</button>

What does this tell us?

By isolating from the CSS framework, we no longer couple our views to any specific framework.

When we want to switch to another framework, we do not have to update all the views. We only have to update the abstraction layer between our views and the framework. And in this case, the abstraction layer is the SCSS file. It's likely to be easier to maintain only that file instead of finding all usages of framework-specific CSS classes across all existing views.

If there's one thing to take away from all of this, it's that isolation principles are valid for every area in our applications.