I caught whiff of some #hotdrama the other day surrounding native CSS module scripts, a new addition to JavaScript — yes, native CSS-in-JS.

Evan You, creator of Vue, had an even less favorable and more candid opinion:

If I can attempt to summarize: There’s an issue in that native CSS modules are encroaching on the same name as a popular GitHub project called CSS Modules which do similar tasks and it creates some confusion in search results and blog posts like this. That’s a social faux pas (aka “dickmove”) and I believe the official term for the native solution is “CSS module scripts”. Commandeering “CSS modules” might be the right call long term, but 🤷‍♂️ still a bit uncool, idk. There’s also some fallout effects of bundlers and other tools that were also attempting to extend the native import syntax1. The larger issue is that while the native solution occupies the same namespace, it solves a fraction of problems that the more mature userland solutions solve.

Evan You, Axel Rauschmayer, and Rich Harris are all people whose opinions on technology I respect. Evan and Rich in particular maintain two popular libraries and to me it’s always worth noting why they say they can’t adopt new native web technologies in their projects. I love knowing where native solutions fall short for them. I think they’re 100% right in their opinion that they can’t adopt a solution that has half the features they need and it’s probably frustrating that the native solution isn’t closer to 1:1 feature parity for them.

A bit about what native CSS module scripts are…

Probably the best write up on this is from Dan Clark at web.dev, Using CSS Module Scripts to import stylesheets. tl;dr— CSS module scripts can import CSS into your JavaScript like ES Modules import JavaScript into your JavaScript.

import sheet from './styles.css' assert { type: 'css' };
document.adoptedStyleSheets = [sheet];

That import creates a “constructable stylesheet” which is a fancy object of parsed CSS Rules that get pushed into the Document’s (or ShadowRoot’s) list of adoptedStyleSheets which are like invisible <style> blocks that get applied to the page. You could not use this import syntax and write your own constructable stylesheet with a template literal if you want. That’s makes this pretty useful to me.

Available in Chrome/Edge 93+.

Can CSS Modules be more like CSS Modules?

One of my favorite parts of CSS Modules (the GitHub project, see how much naming issue sucks) is the robo-hashes and robo-classes that get generated and applied to the components. A lot of people look at <div class="zW_7d2"> and throw up in their mouth a bit, myself included, but this little guy is a unique classname scoped for a singular purpose. That’s what we want for component level style authoring. It does the same thing as BEM; but in the same way that BEM is overly verbose and optimized for humans, robo-classes are overly succinct and optimized for robots.

I personally like the way Vue does scoped styling. It’s a Goldilocks-style middle ground for me. It uses data-hash attributes where the -hash suffix is a randomly generated string per component, like data-87v234k. Then all the elements in the component template get data-87v234k attribute and all the component styles get the [data-87v234k] scope applied to them. It’s great and works well, and imho it’s way better and more intuitive than the Shadow DOM’s approach to style scoping and encapsulation.

So I set off to see if I could emulate that kind of scoping but with native CSS Modules (Requires Chrome/Edge 93+):

See the Pen CSS-Modules-in-CSS-Modules by Dave Rupert (@davatron5000) on CodePen.

I made a function called hashinator(sheet, scope) which takes two parameters, a constructable stylesheet and a scope (some boundary of elements) in which to apply the stylesheet. I generate a hash and apply it both the elements in the scope and the selectors in the stylesheet, then push those updates into the document.adoptedStyleSheets array. Serendipitously, Westbrook Johnson was working on something similar yesterday. Obviously naïve and obviously feature incomplete, but I’m kinda happy with the prototype. You can get the robo-scoping you do with CSS Module-like solutions, but using native CSS module scripts.

Anyways, I feel like this shows me that there’s utility for CSS module scripts outside of the Shadow DOM which is one of the assertions made in the Twitter threads. I get why there’s some cross-referencing happening between Shadow DOM and CSS module scripts. The people working on CSS module scripts are also the Web Component people, and they’re trying to solve a problem Lit has, so there’s probably some historical Web Component beef creating background radiation in this whole conversation.

But. But. But.

But it’s still feature incomplete for people like Evan and Rich. This is super naïve. It doesn’t account for server side rendering and automatic class/hash application is a major concern. It doesn’t account for bundling nuances. I could see a world where a bundler detects the CSS import and ingests that file into the bundle and converts it to the manual way of creating a constructible stylesheet to save a request, but I’m sure it’s not that easy. And the big thing, CSS module scripts also aren’t supported in other non-Chromium browsers yet which is probably a deal breaker. It’s not there yet.

Skating to where the puck could potentially be going…

If I could say one more thing… maybe this problem goes away one day. Miriam Suzanne has a proposal and explainer for a new CSS feature called @scope. What if instead of BEM and instead of robo-classes, CSS can do scoping for us? What if instead of fighting the cascade part of CSS, CSS offered a tool to fine tune and control it. @scope is what I want. Also it has donuts. 🍩

Taking it one step further than Miriam does, I’d even go so far to say that we should trash the entire Shadow DOM style encapsulation implementation and use this instead. This may come as a shock because actually I like Web Components, but for as powerful as Shadow DOM style encapsulation is… it’s a major foot-gun for even the most seasoned CSS professionals I know. But that’s just my 🌶 take on this situation, others may have more informed opinions.

  1. IMO, speculatively extending native import syntax (for CSS or SVG) is probably an anti-pattern, like extending Prototype which led to the 2018’s greatest #hotdrama hit #smooshgate. Nobody asked, but “shimport” is just sitting there in the open.