Web Components Guide
Beta

Styling

Web Components have powerful styling capabilities which make them portable and extensible. Styles declared within the Shadow DOM serve as a Web Component's default styling. They work similarly to writing a user-agent stylesheet.

Shadow Encapsulation: Scoped Styles

Shadow DOM offers a boundary line between styles outside the tree, and styles inside the tree. Styles cannot cross this boundary unintentionally. This is different to regular CSS where a selector can affect every element on a page.

<style>
  p {
    color: deeppink;
  }
</style>

<p>This text is deeppink and not teal because it is outside of the shadow root.</p>

<fancy-p>
  <template shadowrootmode="open">
    <style>
      p {
        color: teal;
      }
    </style>
    <p>This text is teal and not deeppink because it is inside of the shadow root.</p>
  </template>
</fancy-p>

The <p> element within the shadow tree is not affected by the styles declared outside of the shadow tree.

Inheritance

Custom elements abide by the same rules of inheritance as other HTML elements. CSS properties whose default value is inherit will inherit from their parent element, for example font-size, font-family, and color. This inherit property crosses the Shadow DOM boundary. CSS custom properties default to inherit, so they'll cross Shadow DOM boundaries too. Top-level elements within a shadow tree inherit properties from the custom element itself (also known as the shadow host).

<style>
  article {
    color: deeppink;
  }
</style>

<article>
  <h1>This text is deeppink.</h1>
  <article-meta>
    <template shadowrootmode="open">
      <style>
        span {
          font-style: italic;
        }
      </style>
      <span>By Some Person</span>
    </template>
  </article-meta>
</article>

The <article-meta> custom element inherits its color from the <article> element where it's set to deeppink. The <span> element within the shadow tree inherits its color from the <article-meta> custom element which means the value will also be deeppink.

Styling elements outside of a shadow tree

In order to be portable, Web Components can add default styles for the element itself (also known as the shadow host). They can also style slotted elements with some default styles.

Writing default styles for the shadow host with :host and :host()

You can use host selectors to style an element from within the shadow tree. The :host pseudo-class always applies styles to the custom element, aka the shadow host:

<fancy-p>
  <template shadowrootmode="open">
    <style>
      :host {
        display: inline-block;
      }
    </style>
  </template>
</fancy-p>

You can also add conditional styles to the shadow host using :host() selector function. This can be used to style the host so long as it matches the given selector. For example, it can be used to select for hosts that have a given attribute. While :host may apply to <fancy-p> component, :host([extra]) would apply only to <fancy-p extra> elements:

:host([extra]) {
  font-style: italic;
  font-weight: bold;
}
<fancy-p>I not am extra</fancy-p> <fancy-p extra>I am extra</fancy-p>

Chaining selectors after :host

While the :host selector refers to the shadow host element which is outside of the shadow tree, if you chain selectors it will select elements within the shadow tree.

<fancy-p>
  <template shadowrootmode="open">
    <style>
      :host > p {
        color: deeppink;
      }
    </style>
    <p>I am deeppink.</p>
  </template>
  <p>I am not deeppink.</p>
</fancy-p>

Writing default styles for slotted elements with ::slotted()

The ::slotted() pseudo-element selector allows you to write default styles for slotted elements that match the given selector. Specifying a selector can be useful for styling specific elements in particular ways.

<fancy-elements>
  <template shadowrootmode="open">
    <style>
      ::slotted(button) {
        color: deeppink;
      }

      ::slotted(p) {
        color: teal;
      }
    </style>
    <slot></slot>
  </template>
  <button>I am a slotted button</button>
  <p>I am a slotted paragraph.</p>
</fancy-elements>

If you want to target elements in specific slots you can pass an attribute selector which matches the slot:

<fancy-article>
  <template shadowrootmode="open">
    <style>
      ::slotted([slot="title"]) {
        font-size: 2rem;
      }
      ::slotted([slot="subtitle"]) {
        font-size: 1.25rem;
      }
      /* The following will target elements going into the unnamed slot */
      ::slotted(:not([slot])) {
        color: deeppink;
      }
    </style>
    <article>
      <hgroup>
        <slot name="title"></slot>
        <slot name="subtitle"></slot>
      </hgroup>
      <slot></slot>
    </article>
  </template>
  <h1 slot="title">I am the title</h1>
  <p slot="subtitle">I am the subtitle</p>
  <p>I am content.</p>
</fancy-article>

You cannot chain selectors with ::slotted(), so the following will not work:

::slotted(h1) span {
  color: deeppink;
}

Parts: styling a shadow tree from the outside

The CSS Shadow Part API allows elements within a shadow tree to be styled from outside of it. This allows Web Components to be very extensible.

fancy-article::part(header) {
  display: grid;
  gap: 0.25rem;
  padding: 0.5rem;
  border-block-end: 0.125rem solid deeppink;
}

fancy-article::part(content) {
  display: grid;
  justify-content: start;
  gap: 0.5rem;
}
<fancy-article>
  <template shadowrootmode="open">
    <article part="article">
      <hgroup part="header">
        <slot name="title"></slot>
        <slot name="subtitle"></slot>
      </hgroup>
      <div part="content">
        <slot name="content"></slot>
      </div>
    </article>
  </template>
  <h1 slot="title">Hello, World!</h1>
  <p slot="subtitle">I am a subtitle...</p>
  <p>I am content</p>
</fancy-article>

Similar to ::slotted(), you cannot chain selectors after ::part() to select children or siblings. The following will not work:

fancy-article::part(header) slot {
  display: block;
}

How to include default styles for a Web Component

You can load a stylesheet for a Web Component with a variety of methods. Some may be familiar, but others are newer.

Using <style>

You can include a <style> tag within your Shadow Tree, and it'll only apply to the Shadow Tree itself - it won't affect the rest of the page:

<style>
  :host {
    color: deeppink;
  }
</style>

Using <link rel="stylesheet">

Using a <link rel="stylesheet"> element in the shadow tree will allow you to write styles in an external stylesheet.

<link rel="stylesheet" href="/fancy-article-element.css" />

If you do this, the stylesheet will be loaded after the script is loaded. This will likely cause a flash of unstyled content (FOUC). To circumvent this, you can preload the stylesheet like this:

<link rel="preload" href="/fancy-article-element.css" as="style" />

Using Constructable Stylesheets

Constructable Stylesheets are stylesheets which are programmatically created in JavaScript. You can create a new stylesheet using the CSSStyleSheet constructor and set styles with the .replaceSync() method:

const stylesheet = new CSSStyleSheet()

stylesheet.replaceSync(`
  :host {
    color: deeppink;
  }
`)

class FancyArticleElement extends HTMLElement {
  connectedCallback() {
    this.attachShadow({ mode: "open" }).adoptedStyleSheets = [stylesheet]
  }
}

Using CSS Module scripts

CSS Module scripts allow developers to import stylesheets as if they were a module script. To do so, use an import assertion where the type is css and then you can add the imported stylesheet to the adoptedStyleSheets array for the element's shadow root.

import stylesheet from "./fancy-article-element.css" assert { type: "css" }

class FancyArticleElement extends HTMLElement {
  connectedCallback() {
    this.attachShadow({ mode: "open" }).adoptedStyleSheets = [stylesheet]
  }
}