Codeskill

Learn to code, step by step

Shadow DOM boundaries and slots

Shadow DOM: encapsulated markup and styles inside a custom element, and slots for composing light DOM content into the shadow tree. Boundaries matter for design systems, CSS isolation, and knowing what document queries can see.

Open vs closed shadow roots

When a custom element attaches a shadow root, its internal DOM is separate from the page’s light DOM. Scripts and document.querySelector cannot reach inside unless you keep a reference:

class CardPanel extends HTMLElement {
  constructor() {
    super();
    const shadow = this.attachShadow({ mode: 'open' });
    shadow.innerHTML = `
      <article class="card">
        <header><slot name="title"></slot></header>
        <div class="card__body"><slot></slot></div>
      </article>
    `;
  }
}

customElements.define('card-panel', CardPanel);

mode: 'open' exposes element.shadowRoot for debugging and tooling. closed hides it – stronger encapsulation, harder testing. Most design systems use open mode.

Using slots

Slots project light DOM children into defined places in the shadow template:

<card-panel>
  <h2 slot="title">Billing</h2>
  <p>Update your payment method below.</p>
</card-panel>

Unnamed <slot> receives nodes without a slot attribute. Named slots match slot="title" on children. Fallback content inside <slot> shows when nothing is assigned. From accessibility’s view, slotted content often stays in the light DOM tree – screen readers may traverse it as children of the host element. Test rather than assume.

Style boundaries

Styles in the shadow tree do not leak out. Global page CSS does not pierce the shadow boundary by default (except inherited properties like color and font-family). Put component styles inside the shadow root:

shadow.innerHTML = `
  <style>
    .card { border: 1px solid #ccc; border-radius: 8px; padding: 1rem; }
    ::slotted(h2) { margin: 0 0 0.5rem; font-size: 1.25rem; }
  </style>
  <article class="card">...</article>
`;

::slotted() styles direct slotted children from inside the shadow stylesheet – with limits (no deep descendants). CSS custom properties (variables) pierce the boundary: the page can theme components by setting variables on the host.

Events and focus

Events that originate inside shadow DOM retarget so outside listeners often see the host as event.target. Focus can move inside the shadow tree; ensure tab order and visible focus rings work within the component. For form-associated custom elements (advanced), the platform can integrate with forms – check browser support before relying on it.

When not to use Shadow DOM

Skip shadow encapsulation when you need global typography styles to apply freely, when CMS content must be styled by theme CSS, or when SSR tooling cannot yet emit shadow templates (see the next lesson). Shadow DOM is a tool, not a requirement for every custom element.

PreviousWeb Components and custom elements (HTML side)