Abstractions
One principle of OOCSS, or object-oriented CSS, is code reuse. To achieve it, OOCSS proposes extensive use of abstractions to define styles.
An abstraction is a set of basic rules attached to a selector that defines a particular appearance or function. Other selectors can share and extend it. Abstractions allow code reuse, so we not only design faster, but the result is easier to maintain1.
Abstractions work much like abstract classes in programming languages. In stylesheets, they can be extended by composing styles and overriding rules.
/* Framework.css */
.btn {
display: inline-block;
padding: .5em .75em;
}
This abstraction applies the styles needed for an element with the .btn class to look like a basic button. If our design uses different kinds of buttons, we can extend the abstraction. For example, if we need larger green buttons, we add these abstractions:
/* Framework.css */
.large {
font-size: 1.5em;
}
.red {
background-color: red;
}
With those three abstractions, we can have four kinds of buttons:
- buttons
- large buttons
- red buttons
- large red buttons
Decoupling styles from HTML
OOCSS also proposes decoupling styles from HTML through classes. This is quick when designing a site for the first time; in fact, some people skip wireframes and write code directly, in what is known as rapid prototyping.
Following our example, we apply the abstractions with:
<button class="btn btn-large btn-red">Delete</button>
With little effort, we can declare buttons in every size and color. Yet this seemingly harmless code eventually fills our HTML with arbitrary classes. We soon realize that classes are not a natural separation between HTML and CSS: they do not actually decouple styles from structure, because changing an appearance still means touching HTML to add or change classes.
The main issue with classes is that they are not very intelligent. They over-qualify our tags until we create a whole structure of arbitrary classes that we must learn before we can apply styles. If we change framework—or even change versions—the classes we learned no longer work, and we have new classes to learn.
I think the only compelling reason to use classes was as a fallback for old browsers such as IE6, which could not interpret intelligent selectors. That no longer applies.
Intelligent selectors
The
pselector is an example of an intelligent selector in its simplest form. Thepselector is intelligent because it has innate knowledge of semantic classification. Without intervention by the author, it already knows how to identify paragraphs and when to style them as such — simple yet effective.
In his excellent article, Heydon Pickering proposes using semantic HTML alongside intelligent selectors to apply styles. If the W3C already defines standard tags and attributes, why not use them instead of the classes each developer invents? Following our example, with intelligent selectors it becomes:
<button>Delete</button>
And the CSS would be:
button {
display: inline-block;
padding: .5em .75em;
}
button[role=alert] {
font-size: 1.5em;
}
As the article explains, intelligent selectors give us greater control over the elements they apply to. One could argue that the button selector is not as flexible as .btn for style reuse, and that is true: it does not work as an abstraction, since it is impossible to apply its styles to an element other than button. But that is not the job of intelligent selectors. For that, we can use a different approach with maps and abstractions, as we will see next.
The scope of abstractions
We could say that the .btn abstraction is public because it is available to any HTML element. In fact, using CSS alone, we can only declare public abstractions. This is why, before preprocessors, OOCSS was the best option for organized, maintainable code. With the help of preprocessors, we can create what we might call private abstractions: they are not available for direct use in HTML.
To turn the public .btn abstraction into a private one using Sass2, we simply replace the dot with a percent sign.
%btn {
display: inline-block;
padding: .5em .75em;
}
To apply the preceding abstraction, we use @extend:
button {
@extend %btn;
}
Private abstractions, like public ones, are arbitrary. However, they belong exclusively to CSS, which makes it possible to decouple styles from HTML.
@extend or @include
At first mixins may seem like the right choice for abstractions. Although the outcome is sometimes the same, I recommend @extend for these reasons:
- Changing scope requires modifying a single character. For example, we could replace every dot with a percent sign in Twitter Bootstrap and most abstractions would keep working.
- It can be declared several times, adding rules and overriding them when necessary.
- It generates less code because, when used more than once, rules are only added to the base selector.
It also has several drawbacks:
- In large projects it is difficult to preserve the order in which styles are applied, because compiled code follows declaration order.
- Applying media queries is complicated.
More experimentation is needed, but from what we have seen I consider @extend the right choice.
Maps
A map is a selector—preferably an intelligent one—that uses private abstractions to define its styles.
Like public abstractions, private abstractions can also be combined. Returning to our example, once we add an intelligent selector, we have a map:
button[role=alert] {
@extend %btn;
@extend %btn-red;
}
We have the same style combinations as with the public abstractions from the beginning, but our HTML is entirely semantic:
<button role="alert">Delete</button>
Maps are the bridge between OOCSS and intelligent selectors. They are the layer that gives us semantic HTML, intelligent selectors, and fully portable abstractions.
Organizing files
Another advantage of preprocessors is that they let us organize styles across several files; in the end, everything can compile into a single minified file.
Because maps serve a specific purpose, they should live in separate files. I think an organization like this could work:
├ styles
├────┼ abstractions # Private abstractions that can be shared across projects
| ├──── _buttons.scss
| ├──── [...]
├────┼ maps # Project-specific style maps
| ├──── _navigation.scss
| ├──── [...]
├──── main.scss # File that imports maps and abstractions
Conclusion
One of the most important applications of the OOCSS approach is frameworks that contain predefined styles for nearly every graphical interface element, including Twitter Bootstrap and Zurb Foundation.
On the other hand, intelligent selectors enrich our selector toolkit and remove the dependency on classes. They may seem like two irreconcilable approaches, but private abstractions and maps can bring them together into frameworks as complete as Bootstrap—yet easier to maintain, portable, and above all respectful of semantic HTML.
Footnotes
-
More information in Nicolas Gallagher’s article, “About HTML semantics and front-end architecture”. ↩
-
Sass, Less, and Stylus all support private abstractions, although their syntax differs. ↩