The CSS mental model

I am likely going to write a “CSS for JavaScripters” book, and therefore I need to figure out how to explain CSS to JavaScripters. This series of article snippets are a sort of try-out — pre-drafts I’d like to get feedback on in order to figure out if I’m on the right track.

Today we will attempt to describe the different mental models for CSS and JavaScript. Everybody agrees there is a difference, but nobody’s able to define exactly what that difference is. So let’s try.

(Also, at the last moment I switched from describing CSS as “context-based” to “state-based.” I hope that makes more sense, and it’s one of the points I’d like feedback on.)

CSS and JavaScript mental models

Programming CSS requires a different mental model than programming JavaScript. CSS is a declarative language, while JavaScript is an imperative language. (Also, it’s an open question whether CSS is a programming language at all. We’ll get back to that later, but for now we’ll pretend it is.)

Those who have experience with declarative languages such as Prolog, or even spreadsheets, will have the advantage over people who only know imperative languages, who may be confused by CSS at first, since it lacks many of the control structures they’re used to in JavaScript.

The differences go deeper, though. Fundamentally, JavaScript execution is time-based. That is, everything happens in the order prescribed by the program. There is a time, at the very start of execution, that a certain variable does not exist yet. Then it’s defined and assigned a value, and later that value is changed, and so on. An if-statement based on that variable will have different outcomes at different times.

Not so CSS. All CSS declarations get their value at the same time, and they all take effect at the same time. It is impossible for any CSS declaration to be applied earlier than any other CSS declaration. Declaration order matters, but any conflict is resolved immediately and doesn’t require (or even allow) control structures. The same CSS will always give the same result.

State-driven change

[Could I even say “state-based programming?”]

CSS does accept changes to the initial rendering of the page, but it is fundamentally state-driven. It can only react to a limited number of well-defined state changes in a web page. A good example is changing a background color on hover.

nav a:hover { background-color: red; }

When the link’s state changes (i.e. the mouse pointer hovers over it), CSS switches from one set of instructions to the other, and when the state changes back to no-hover, CSS switches back to the original instruction set.

You can achieve the same effect with a few lines of JavaScript, but it’s instructive to study the differences.

el.onmouseover = function () { this.style.backgroundColor = 'red' } el.onmouseout = function () { this.style.backgroundColor = ''; }

First, something that’s so obvious that it’s hardly ever mentioned: JavaScript needs CSS in order to actually change the background color. It is impossible to do this in any other way. Conversely, the CSS declaration does not need JavaScript. When it comes to styling, CSS is more fundamental than JavaScript - closer to how browsers actually work, and thus much faster.

Next, JavaScript needs two statements instead of CSS’s one. It does not detect a state change automatically, as CSS does. Instead, you have to take it by the hand and guide it through all possible options, and make sure it notices not only the state change, but also the change back.

Then, JavaScript needs an extra control structure to make sure that the state change is detected on all links in the navigation instead of just one. This is not particularly difficult, but it needs to be done. Again, with its single selector CSS is more elegant.

Still, JavaScript has its advantages as well. Unlike CSS, JavaScript allows you to cancel the background change by adding an if-statement to determine whether the link background change should take place. In CSS you defined the styles to be applied in a state of hover, and those instructions are always followed. If you want more fine-grained control, JavaScript is the better solution.

Elegance vs control

More fine-grained control is not an end in itself. It all depends on the context. Sometimes, like in the background example, the simple, elegant, fast solution is better. In more advanced situations there comes a time when JavaScript, with its fine-grained control, becomes the better answer.

Keith Grant pointed out an interesting analogy. If we as humans wish to run, we just tell our body to run, and it obeys. We could deconstruct the act of running into its constituent parts like “raise left knee,” “raise left foot,” “advance left leg,” and so on, but that is much harder to do and we’d likely fall over, as this game shows.

In this analogy, the run command would be CSS, and the detailed foot-and-leg instructions would be JavaScript. The analogy is flawed, as all are, since as humans we’ll almost never operate in a context that requires us to deconstruct the act of running, while a web page might benefit from deconstructing the act of changing background colors. Still, the analogy might help you to understand the advantages of the CSS mental model better.

It doesn’t answer the most important question, though. When do we cross the line? When does JavaScript become the better option? In the end, that depends on your context, both the project you’re working on and your familiarity with CSS and JavaScript. Still, I have the feeling that JavaScripters who are unfamiliar with CSS tend to draw the line too early, and that their simpler use cases might be better served by a pure CSS solution.

The choice is yours

So the choice is yours. Elegant, but limited, state-driven simplicity versus controlled, but complicated, time-based execution. There is no right or wrong here — just a careful weighting of options and contexts.

Whatever you choose for a particular project, if you work with browsers you should have a basic understanding of both approaches. And if you want to master CSS you need to understand state-based programming and the mental model that goes with it.

Feedback

Badly formatted

https://twitter.com/ptrdo/status/1100425471802585088

This is a very good approach. Perhaps another aspect of declarative v imperative (assumed but not mentioned) is how declarative rule-setting defines characteristics which can be universal and prescribed without the elements needing to exist, while imperative expects an instance.

https://twitter.com/tabatkins/status/1100421543144841216

The bit about "needing an extra control structure" could use some slight expansion. You need a loop over all links inside of nav (itself probably obtained with a selector), then a mutation observer to *add* the listeners for new links and *remove* them if the link is moved.

State machines: https://medium.com/@DavidKPiano/css-animations-with-finite-state-machines-7d596bb2914a

https://adactio.com/links/14866

I’m not sure if I agree with describing CSS as being state-based. The example that illustrates this—a :hover style—feels like an exception rather than a typical example of CSS.

https://twitter.com/leifastrand/status/1100801652586659843

Declarative = you describe the intended outcome. Imperative = you describe the steps to take. JS devs often encounter something similar: use map, filter and reduce over a collection to define intended result, or imperatively define the steps to take as a for loop.

Mail

JS is verbs, CSS is nouns.