https://twitter.com/snookca/status/1073299331262889984?s=21
“In what way is JS any more maintainable than CSS? How does writing CSS in JS make it any more maintainable?”
Happy to chat about this. There’s an obvious disclaimer that there’s a cost to css-in-js solutions, but that cost is paid specifically for the benefits it brings; as such it’s useful for some usecases, and not meant as a replacement for all workflows.
(These conversations always get heated on twitter, so please believe that I’m here to converse, not to convince. In return, I promise to listen to you too and change my opinions; I’ve had mad respect for you for years and would consider your feedback a gift. Also, some of the stuff I’m writing might seem obvious to you; I’m not trying to tell you if all people of some of the details, but it might be useful to someone else who bumps into this who doesn’t have context)
So the big deal about css-in-js (cij) is selectors.
The biggest win from cij is that computers generate selectors for you automatically. While strategies like oocss, etc are conceptually great, relying on selectors to enforce those architectures are hard in a global namespace because there is always a chance of a clash; if not now, 3 years in the future when the team has changed and become bigger and whatnot. This is exacerbated with third party libraries. (In fact, scoping selectors is pretty much the only thing css modules does, and it’s hugely popular and useful for just this.)
Further, it’s extremely hard to statically analyse selectors to see whether any rules are broken (or even a typo!) this means constraints have to be enforced via code reviews and education, which is a harder problem to solve and scale. We should use a computer's assistance us on this, and not rely on humans being infallible (they’re not!)
The enforced control over selectors enables some new capabilities that we couldn’t do easily before; eg - we can trivially extract critical css for html by matching the ids to rules, meaning we can load a page with just 1-2k of css that the page needs for initial render. With no runtime! Frameworks like gatsby and next utilise this heavily for the kind of performance numbers they boast of. Inlining that critical css sirectly into the html is also simple now because it’s so small, removing a whole blocking request for the page. Amazing for boosting time to first render/interactive. This also alleviates many concerns about the js bundle size! The savings come from this critical css approach, along with using dynamic import()
s and code splitting and actual dead code elimination (as opposed to append-only stylesheets that grow over time - some real data here https://twitter.com/mrmrs_/status/874336748528177152?s=21)
Similarly, theming. You know how css vars, despite being so fun, never really took off for the usecases it was designed for, simply because fallback strategies for older browsers are either too hard or limiting? It’s meant that they’ve been usually used for top level variables, but rarely as generic variables to be filled in whenever. Runtime evaluation means you can mix and match js variables in their place, and truly achieve that dream.
In fact, you’ll love this; with this approach, you can truly, finally, polyfill css variables for all browsers. ie - zero runtime css vars for browsers that support it, and a special runtime build for older browsers (I wrote about it in a little more detail here https://gist.github.com/threepointone/0ef30b196682a69327c407124f33d69a and iiuc, this is the only lib that’s ever managed to do this.)
In an SPA/component world, where assets like scripts and styles are loaded asynchronously, you can’t guarantee style sheet loading order, which means you have to lean on some form of runtime composition to guarantee a shape, else you end up with !important
rules even if you follow a given architecture. To clarify - if you have class=“a b”
on an element, and a
and b
are defined in different stylesheets, you can’t be certain of the order of the cascade. The facebook codebase has thousands of !important
statements, despite being written by competent engineers with solid engineering practices and deep relationships with design teams.
You mentioned treating css like js - consider that 10 years ago, we were solving similar problems for js - libraries and modules were registering themselves into the global namespace ($
, etc), and we had to be very particular about the order that script tags were included into html. But we didn’t rely on convention forever - we eventually moved to modules, and build systems to stitch them together to guarantee order and such. These libraries simply enforce this invisibly.
(One of my observations was that you can still actually implement css architectures (like oocss, smacss, etc in the cij world - elements from those architectures are represented as objects instead of selector+rule units. I use this approach and it serves me well. Wrote something on it here - https://gist.github.com/threepointone/9f87907a91ec6cbcd376dded7811eb31)
Now I’m well aware of the drawbacks of css-in-js. Indeed, that’s why there’s no “canonical” library that truly represents css in js - it’s a spectrum of libraries with completely vanilla static css on the one end, and completely dynamic libraries like styled-components on the other end). (Ps- preprocessors like sass
and less
feel orthogonal to this spectrum imo, since they can theoretically be used with any of these libraries as an authoring concern.) Each of these libraries have tradeoffs - some do static extraction so there’s no runtime cost, some are tuned for correctness, some for the “developer experience”, others to efficiently do difficult animations, etc etc. This is a reaction to the industry's need for solutions for different usecases, in a hyper-evolving industry. Not just these libs! Even standards folks (via shadow dom etc) are making a valiant effort to solve some of these problems, and it has its own set of tradeoffs, least of which is that it isn’t widely available yet, and the buy in is not acceptable by many teams.
I used to feel very strongly about this, but I’ve tempered my thoughts about this over the last few months. It turns out that the “truth” really lies somewhere in the middle - it’s a function of teams, needs, time, documentation, and so much more. Further, I don’t even think these represents the “final form” of the idea. We should be encouraging of experimentation in this area, if only to learn what we could do better, and maybe even land some of these ideas in browsers.
I’ve written all this out on a phone in the middle of the night, so it might have mistakes/assumptions that I got wrong, but I’m happy to correct and revisit. But I do hope I’ve shown you some perspective to why some teams would use this family of approaches.
I too have been making websites since the mid-90s (long enough to remember being excited about the CSS Zen Garden). I've also used SMACSS. These days I identify as a "full-stack" developer but that doesn't mean I don't know what I'm doing (I just had to ping-pong all over the stack throughout my career). I'd like to make the case that the main benefit of CSS-in-JS isn't actually technical, it's social.
Today CSS-in-JS no longer means manipulating inline styles, it mostly means generating stylesheets dynamically and colocating CSS rules with JS components. Depending on the flavor this may indeed mean plain JS objects with camel-cased properties, or it may mean actual CSS code (with nested selectors) in JS template literals. In many ways, CSS-in-JS is an evolution of patterns like SMACSS or BEM for interactive components (i.e. components defined in terms of both of CSS styling and JS behavior).
The big problem with SMACSS and BEM is that they both still require humans to come up with globally unique names. This means generic component names like "Button" or "Header" quickly become misleading in any but the most cookie-cutter designs. What may start out as "the Button" may soon become just "the button we use on the profile page", as opposed to "the new button we now use everywhere else" (but not on the profile page because that button needs to look a certain way for arbitrary business reasons).
The obvious solution is to rename the old component into something more specific so the new component can have the more generic name but that requires a global rename across CSS, HTML and JS (plus templates and possibly across multiple projects) and this can easily go wrong in many ways (not to mention having to communicate this change across possibly multiple teams).
The alternative is "just picking better names" but the actual use of a component may change over time and it may not always make sense to reuse the existing component even when the new component really looks like it's just a variant of the old one.
When developing the JS side of components it really doesn't matter what the components are called and that's a good thing: this makes it possible to refactor JS code to merge convergent components or split divergent variations of one component into separate components, the implementation of the component is mostly an implementation detail.
Alternatively you can use a system like SMACSS alongside a JS component library and just map the component names between the JS/DOM and CSS side but aside from the overhead of always having to look in multiple places this still requires manually naming things and avoiding global naming conflicts.
Even if we disregard the problem of having a single global namespace (which btw even Web Components seem to be beginning to work around lately by letting the user decide what name to register a component with) naming things is hard. Having a human come up with nonsense like "mediabox_widget_inner_wrapper" (or overly specific rules like
.mediabox > div > div > div
that make the component's inner structure a nightmare to refactor) doesn't provide any benefit compared to just having a CSS-in-JS library call it "MediaBox_a23bef6" (especially if some of the people who have to name things are non-native speakers and may struggle coming up with yet another synonym for the word "wrapper").Before someone points it out: yes, CSS modules solve some of the same problems. There are technical differences (e.g. without being able to rely on CSS custom properties it's easier to share variables in CSS-in-JS than when using CSS modules) but for most intents and purposes I think it's valid to think of CSS modules as a special case of CSS-in-JS (that it uses a separate CSS file rather than colocating the CSS rules in the JS file is an implementation detail).