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?