Home / Articles /

Styling a Web Component

This confused me for a bit here so I'm writing it out while it's fresh in mind. Just because you're using a web component doesn't mean the styles of it are entirely isolated. You might have content within a web component that is styled normally along with the rest of your website. Like this:

See the Pen Web Component with Global Styles (because no Shadow DOM) by Chris Coyier (@chriscoyier) on CodePen.

That <whats-up> element isolated the JavaScript-powered functionality of itself by attaching a click handler to the <button> inside of it. But the styling of that button comes from global CSS applied to that page.

Moving the template inside the web component

But let's say we move that <button> into the web component, so we can use <whats-up> all by itself. We could do that by .innerHTML'ing the custom element:

See the Pen Web Component with Global Styles (because no Shadow DOM) by Chris Coyier (@chriscoyier) on CodePen.

Again, entirely styled by the global CSS. Cool. That may be desirable. It also might not be desirable. Perhaps you're looking to web components to isolate styles for you.

Shadow DOMing the template

Web components can isolate styles (and abstract away HTML implementation) via the Shadow DOM. Here's that same component, using Shadow DOM instead:

See the Pen Web Component with Local Styles by Chris Coyier (@chriscoyier) on CodePen.

Note that the functionality still works (although we had to querySelector through the shadowRoot), but we've totally lost the global styling. The Shadow DOM boundary (shadow root) prevents styling coming in or going out (sorta like an iframe).

Shadow Root

There is no global way to penetrate that boundary that I'm aware of, so if you want to bring styles in, you gotta bring them into the template.

Move the styles (inline) inside the web component

See the Pen Web Component with Local Styles by Chris Coyier (@chriscoyier) on CodePen.

This would be highly obnoxious if you both really wanted to use the Shadow DOM but also wanted your global styles. It's funny that there is a Shadow DOM "mode" for open and closed for allowing or disallowing JavaScript in and out, but not CSS.

If that's you, you'll probably need to @import whatever global stylesheets you can to bring in those global styles and hope they are cached and the browser is smart about it in such a way that it isn't a big performance hit.

Link to external styles instead

I'll use CodePen's direct link to CSS feature to import the styles from the Pen itself into the web component:

See the Pen Web Component with Local Styles by Chris Coyier (@chriscoyier) on CodePen.

Apparently, there is no way to avoid somewhat of a Flash-Of-Unstyled-Component this way though, so inlining styles is recommended until there is.

Custom properties go through the shadow DOM

Another important thing to know is that CSS custom properties penetrate the Shadow DOM! That's right, they do. You can select the web component in the CSS and set them there:

See the Pen Web Component with Custom Properties by Chris Coyier (@chriscoyier) on CodePen.

HTML you point to via a slot is globally stylable

So if you have like:

<my-module>
  <h2 slot="header">My Module</h2>
</my-module>

And where you define your shadow DOM you use that header:

<div class="module">
  <slot name="header"></slot>
</div>

Then the <h2> will be globally stylable, but that <div class="module"> will not.

See the Pen Slots and styling web components by Chris Coyier (@chriscoyier) on CodePen.

::part and ::theme

I didn't investigate this too much because this is a spec that's still being worked on I guess, but will likely ultimately play a large role here. Monica Dinculescu covers it in detail in her article ::part and ::theme, an ::explainer.

Looks like a way to reach into the shadow DOM, but only to the exact level that matches, no deeper.

<h4><x-foo>
  #shadow-root
    <div part="some-box"><span>...</span></div>
    <input part="some-input">
    <div>...</div> /* not styleable
</x-foo></h4>
x-foo::part(some-box) { ... }

/* nope */
x-foo::part(some-box) span { ... }