Adopt a Design System inside your Web Components with Constructable Stylesheets

Style won’t infect our component from the outside, and style from our component won’t affect the outside world.

Notice in the code snippet above, where we’re styling a button with no extra class specificity.

This isn’t just a simple example, it’s fairly routine not to worry about doing something like this because only the buttons in this Web Component are styled this way.

Similar with the span with an ID.

You’d never use the ID attribute like this in a small UI component because the ID has to be unique to the page.

Not so with the Shadow DOM, the ID only needs to be unique within the component.

Using the Shadow DOM and Web Components is like going back to simpler times when web development wasn’t so complex and fragile, because we’ve redefined the scope of a huge application or page, to a much smaller and manageable one.

But, there is a major missing piece in all of this.

The missing piece is a Design System, and that’s the rub.

We want to bubble wrap our component and protect it from all outside style, yet at the same time, we want just the right style to come in and make the contents of our component look like the rest of the application or page.

CSS Vars are just the about the only established way to do this, but doing things one variable at a time is a Sisyphean task.

CSS Vars are allowed right through the Shadow Boundary into your Web ComponentWedging a Design System into a Web Component today means likely exploding an established CSS system into pieces, turning the bits into Javascript strings, and figuring out a way to bring them all together in a meaningful way inside your component, only loading the bits you need.

The other bad thing with this approach, is that you’re creating a design system from scratch in each and every component instance on your page.

Its tons of duplicated CSS inside every mini-DOM.

Constructable StylesheetsThere are two brand new browser features poised to solve this problem.

The first is CSS Shadow Parts/Theme.

After spending a little time experimenting with Shadow Parts, it became clear that there is a lot of work to do around changing existing CSS to use “part” attributes in addition to classes.

The design system is just one piece of the puzzle.

There’s also a lot of onus on the Web Component developer to “export” parts through the the component into child components.

The Shadow Theme feature sounds like it alleviates some of this, but unlike Shadow Parts, it’s not even supported by Chrome yet while Shadow Parts are only supported in Chrome right now.

The better option is the brand new “Constructable Stylesheets”.

It’s not just better IMHO, it’s pretty close to perfect, and I think is poised to bring us back to our basic CSS roots in the Web Component world.

Not, only is it already available in Chrome, but is easy to polyfill as well.

Constructable Stylesheets are an evolution rather than a brand new feature.

Really we’re just extending the API of the Javascript CSSStyleSheet object.

So, what's new?It used to be that after creating a new stylesheet, you could only edit the list of CSS rules.

Now, though, you can replace the entire sheet, wholesale.

const sheet = new CSSStyleSheet();sheet.

replace(`@import url('directory/cssfile.

css') .

then(sheet => {}) .

catch(err => {});Note that the above is using the async replace method.

For loading stylesheets with the @import directive, the CSS won't be immediately loaded.

That said, the new stylesheet is available right away.

The next question to answer is what can we do with that stylesheet?.Well, now in Chrome, both the document and shadowRoot objects have an adoptedStyleSheets property.

This property accepts an array of stylesheets.

So now, a CSS file, or multiple CSS files from a design system can be adopted by any number of Shadow DOM enabled Web Components on a page.

Not only that, but these style sheets aren’t copies — you’re not loading your Web Component instances with tons of cloned design system instances as is the case today.

Every component (and the document) can share the same sheets, as well as pick and choose which CSS to adopt.

Stylesheets can be instantiated and adopted by the document object or your Web Component’s shadowRootConstructable Spectrum and Style ShelterI hope you’re thinking this sounds as promising as I do!.In theory, we can take a complete and unchanged design system and use it in Shadow DOM enabled Web Components!.Instead of just writing a blog post that this is theoretically possible, I took that challenge on with a real design system.

I just so happen to work as a prototyper at Adobe and love using Web Components in my work.

Adobe’s design system, Spectrum, is something I use almost every day.

Of course, I haven’t been able to use Spectrum in conjunction with the Shadow DOM, so I was really excited at the prospect of getting this to work.

Spectrum itself is pretty awesome, too.

It’s recently been reworked with CSS Vars as the basis of everything.

And then, if a monolithic design system isn’t what you’re after, individual components are delivered as well.

With Spectrum, a developer can layer on CSS Vars, the Spectrum base, the theme (light/dark variations), and finally a handpicked set of component CSS.

Layers of Adobe’s Design System, SpectrumNo really, I don’t just think Spectrum is awesome because I work at Adobe.

It’s awesome because this fits extremely nicely with Web Components and Constructable Stylesheets.

Each component can use some simple JS logic to adopt exactly the CSS it needs.

Every component adopts the base CSS Vars and base system style.

We can choose which theme to use and load those files as well.

Last, each component should know exactly which Spectrum UI components it uses, and also load those CSS files.

This also means that the index.

html page doesn’t need to know anything about what components need to be included, nor link to any stylesheets itself.

Every Web Component is completely self reliant.

All that’s missing is a global module that can keep a cache of all loaded sheets.

Web Components can pull from this module, and if a CSS file has already been loaded, it will just deliver the cached sheet back.

Before jumping in and getting Spectrum working inside my Web Components, I went to work and created Style Shelter (also available on NPM).

In addition to caching, most sheets need to be adopted by the Web Components, but some (root level CSS Vars) need to be adopted by the document, so Style Shelter also handles adopting different sheets to different scopes.

I’m excited to say that my challenge to use Spectrum without changing any CSS worked like a charm!.I knew I had to be thorough, too.

Every CSS component needs to work, so I forked the Spectrum CSS repo and created a Web Component based demo page.

I did run into some nuances to solve that were Spectrum specific, but you can read all about those details on the project’s readme.

Browser SupportSo, browser support makes us come crashing back to planet earth.

Right now only Chrome (and one would assume the new Chromium powered Edge) supports Constructable Stylesheets.

Firefox and Safari supposedly are considering or are working on the feature now, however.

Good news, though!.There is a polyfill, and it’s easy to use.

The only downside is that styles are copied over and over again, just like I promised we didn’t need to do.

Take this Shadow DOM in Chrome, and notice that even though the component is styled perfectly, there’s no style in shown — it’s all adopted.

Now, compare that to Firefox.

With the polyfill, the component is styled the same, but we can see all the adopted styles copied to the Shadow DOM.

So, hopefully Safari and Firefox deliver the goods reallllllll quick!.Delivering an entire design system to a Shadow DOM with no changes is a really big deal.

And I’m probably pushing my luck, but I’m going to need to ask all the browser vendors to deliver CSS Modules, too.

CSS ModulesThe reason I want CSS Modules is not design system related.

At the start of this article, I stated that I wanted plain, simple CSS again.

Actual files, not CSS inside JS strings.

I think it’s incredibly important that a well-built and shareable component be self-contained and not dependent on anything in the outside world.

You might guess we can use Constructable Stylesheets here too, but there’s a small complication.

In my Constructable Spectrum demo, I do just that.

I load up each component’s local style as an actual CSS file to be adopted.

The problem is that stylesheet @imports are relative to the main index.

html.

So instead of pointing to .

/mycomponent.

css, I need to use the full path to my component’s CSS from the root of the project.

Not great.

Web Components should not need to know where they live in a project to function.

They should be able to be moved around and used anywhere without thinking about these things.

JS modules, however, are loaded relative to whoever imported them.

CSS Modules should be the same, and theoretically, you’ll get a CSSStyleSheet back…ready to be adopted.

A nice bonus would be if the same CSS file is imported, it would be a reference to the same one that was loaded from a different Web Component.

I don’t know if that’s the case in the spec, but it would certainly be AMAZING.

The Constructable Stylesheet approach is just gaining steam now and only supported in Chrome.

Because of it’s uncertain future, I really couldn’t put them in my Web Components in Action book.

That said, I’m excited that approaches like what I’ve outlined are a natural extension of Web Components today.

With the Shadow DOM, Web Components, Constructable Stylesheets, and possibly CSS Modules, we’ve got something great here.

We’re on the verge of getting simple and easy to use CSS back, and it’s exciting!.. More details

Leave a Reply