Over time I discerned a landscape of common features. There are structures you learn to recognise. Those structures form your mental map of how a programming language should work. You find shortcuts through this landscape. These shortcuts are necessary to become productive as a programmer, but they sometimes have undesired side effects.
Not every type of language in computing is a programming language — one in which you can express an algorithm. Markup and stylesheet languages have different roles and capabilities; they are concerned with semantics and presentation. There are things that you simply cannot do in HTML or CSS. There are things you can only do in HTML or CSS. These languages have their own landscapes which are quite distinct from programming languages, although they can share some superficial features.
I remember quite clearly the frustrations I encountered first learning CSS. Setting rules only to have them ignored; setting rules only to have them do something completely unexpected; tracing back up through the cascade, trying to discover why one rule was overwriting another. We’ve adopted strategies like BEM to making styling easier, but it is no surprise to me that people first encountering CSS — particularly from a programming background — find it frustrating. The mental maps I’d created whilst programming, together with the superficial similarities of CSS, led me to assume that things should work the same way, when they demonstrably didn’t.
In a typical programming language, declarations take effect precisely as you specify them, and remain in effect until you instruct them otherwise. In CSS, a rule can be declared, but if certain other conditions are not met, the rule will not be applied. Not only do you have to learn the rules, you also have to learn the conditions under which they might apply. Add the cascade, which has its own set of calculations regarding conflicting rules. Add inheritance, which specifies default rules when nothing else applies. The complexity adds up rapidly. However, most of this complexity exists for a reason.
All stylesheet languages have a set of requirements, and stylesheets on the web have even more. On top of typical stylistic rules, the language must also be able to negotiate between conflicting stylistic preferences: a user might have fonts on their browser set to a large size to help with legibility, whereas the developer may have set them to be much smaller. The language must be robust enough to still display something sensible if the style resource doesn’t load. It must also be able to handle different devices, resolutions, and sometimes arbitrary content. Nowadays, well-written sites are written responsively, so not only does CSS handle styles, it handles these styles as they change across space and time.
To emphasize: CSS isn’t a programming language. It’s a stylesheet language. We shouldn’t expect it to behave like a programming language. It has its own unique landscape and structures, ones that people with programming language mental maps might not expect.
I believe that this mismatch of expectation is what has led to the current explosion of CSS-in-JS solutions. Confronted with a language that seems arbitrary and illogical, and having spent little or no time exposed to the landscape, developers dismiss CSS as ‘broken’ and use systems that either sweep it under the rug, or attempt to force it into alignment with the landscape of a programming language — often sacrificing some of the most powerful features of CSS.
This is not to say that I think CSS-in-JS is necessarily a bad thing. CSS Modules and the ICSS Spec are an excellent attempt to deal with one of the major drawbacks of CSS — its global-by-default nature. Many systems do a good job of creating and inlining critical CSS whilst extracting the remaining rules into separate stylesheets. Unfortunately, this usually depends on proper configuration, which means that the default implementation pushes inline styles. There is often a cost to writing CSS this way, expressed in heavier HTML pages with no caching of styles. This cost is then paid by the end user.