CSS has Weak Forms of Abstraction and Combination

Summary: According to the requirements proposed by Abelson and Sussman, CSS does not provide adequate means of combination and abstraction to be considered a powerful language.

I am trying to improve the maintainability and reusability of my CSS over the longterm. I've written about how to organize CSS before. I've learned a lot since I wrote that. I've tried lots of things and talked to lots of people, I finally seem to have found a conceptual framework to capture my new understanding. I'm trying to explore it here. Comments are welcome.

I'm going to take a cue from the first page of SICP and analyze CSS as a language.

Abelson and Sussman in SICP 1.1 (italics mine):

A powerful programming language is more than just a means for instructing a computer to perform tasks. The language also serves as a framework within which we organize our ideas about processes. Thus, when we describe a language, we should pay particular attention to the means that the language provides for combining simple ideas to form more complex ideas. Every powerful language has three mechanisms for accomplishing this:

primitive expressions, which represent the simplest entities the language is concerned with,

means of combination, by which compound elements are built from simpler ones, and

means of abstraction, by which compound elements can be named and manipulated as units.

Let's analyze CSS in terms of these three mechanisms.

Primitive Expressions

The simplest entities the language is concerned with are properties and primitive selectors. CSS properties, though they have a property name and property value part, are meaningless if split up. Primitive selectors include element name selectors (body, a, div), class name selectors (.main-wrapper), id selectors (#login-form), and pseudo-class selectors (:hover), among others. Properties appear inside the rule body ({}), while selectors appear before the rule body. The two are semantically and syntactically separated.

Means of Combination

Properties can be combined in two ways. First, multiple properties can be put inside the same rule body. This is the most obvious and most readable form of property combination. The second form is harder to reason about. It occurs automatically within the browser during rendering. That form of combination, involving the application of multiple rule bodies to the same HTML element, uses a complex ordering of properties from all bits of CSS and element styles on the page.

Tomes have been written about how difficult it is to reason about this automatic form of combination. Usually, the answer is limiting it (or avoiding it altogether) through programmer discipline, with varying degrees of success.

Primitive selectors can be combined in several ways. Without spaces between them, multiple selectors will intersect, meaning they target elements more specifically. div.main-container will target div elements that ALSO have the class main-container.

With spaces, multiple selectors indicate nesting. div .main-container matches any element of class main-container within any div. There are several operators which combine them in different ways (> indicates direct nesting, etc.). Nested selectors are associated with CSS that is strongly coupled with the structure of the HTML it is styling and therefore less reusable.

Selectors that are combined with commas create a group. These compound selectors will match any element that matches at least one of the component selectors. header, .header will match all header elements and all elements with class header.

There are more types of selector combintation operators, but they are more specialized and less frequently used.

The locus of combination, for both properties and selectors, is the rule. The rule has one compound selector and zero or more properties. Rules with zero properties have no effect.

Means of Abstraction

The means of abstraction in CSS are quite limited. There is no way to name anything. People lament the lack of named values (often refered to as variables) or named styles (sometimes called mixins). Naming is out in CSS.

The only means of abstraction is the class and id, which are labels that can be applied to HTML elements. With an id or class (or combinations), you can target precisely the elements you need to and achieve some reuse. For instance, I can "reuse" the #login-form id selector in two different rules. I can also add the class rounded-corner to two different HTML elements, effectively "reusing" the same rule twice. By a very disciplined use of class selectors by combining them with commas, one can apply "rule bodies" as a unit in a very limited way, though it is impracticable in practice.

The disadvantage to this technique of using id and class selectors is that the HTML must be modified when styles change, defeating the purpose of using CSS for content/style separation. There is a lot of discussion about using semantically named classes. For instance, call the button login-button instead of green-shiny-button. This is thought to be more robust in the face of style changes, but requires existing CSS to be thrown away in order for the page to be redesigned. CSS offers no good way to modify HTML and CSS independently.

Conclusion

CSS does not meet the criteria for a "powerful language" as used in SICP. This is no surprise. The reasonable means of combination are limited to the rule. The means of abstraction are almost non-existent. There is no way to name anything. And the other form of abstraction (ids and classes) provides no way of reusing both the HTML and the CSS. It is obvious why CSS is typically unmaintainable. With the current crop of compile-to-CSS languages (commonly known as "CSS Preprocessors"), there is hope that better means of abstraction are possible. How will compile-to-CSS languages fare in this same analysis?