Skip to content

Instantly share code, notes, and snippets.

@threepointone
Last active August 26, 2023 15:43
Show Gist options
  • Save threepointone/731b0c47e78d8350ae4e105c1a83867d to your computer and use it in GitHub Desktop.
Save threepointone/731b0c47e78d8350ae4e105c1a83867d to your computer and use it in GitHub Desktop.
For Snook

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.

@threepointone
Copy link
Author

I consider css modules to be well and truly css in js. (Fun fact - one of the creators of css modules, glen, is also one of the creators of styled-components). It matches some key characteristics -

  • rewrites selectors to be scoped
  • usually inlined into js bundles (especially when using import()s
  • exposes/exports a js interface/keys

but it doesn’t solve “all” the things. Specifically, the class=“a b” problem, particularly when the stylesheets are loaded asynchronously. If your setup doesn’t have this constraint, you’re gold.

As for css vars - I use india as a benchmark for whether a feature is ready to be used. caniuse has it at <80%, which disqualifies it for me (and also why I even worked on correctly polyfilling it) further, you can only use css vars for property values, and not whole rule sets. There was a proposal to do so (@apply, I mention it in the linked gist), but that’s dead now.

@ahmadawais
Copy link

That's a great write-up, Sunil! Will be referring people to this gist for when we have the same discussion. 👍

@catamphetamine
Copy link

No, CSS is better than the so-called "CSS-in-JS".

@sonhanguyen
Copy link

sonhanguyen commented Dec 30, 2020

No, CSS is better than the so-called "CSS-in-JS".
@catamphetamine

if you're so insist on the CSS syntax itself then try styled-components. CSS modularity is broken evidenced by the endless stream of CSS methodologies trying to fix it that are still coming until this very day. That's the main motivation for cij, unless you have some argument to counter it?

@kvedantmahajan
Copy link

kvedantmahajan commented Feb 10, 2023

My Takeaway - Go for orthogonal solutions such as SCSS and newer elegance TailwindCSS. Choose from the both two as of now depending on your project/library bundle requirements. Rest of the things are great to read about evolution of CSS-in-JS. While it is true that this gist is bookmarked for lifetime.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment