Shadow DOM Slot API Reference

<slot>, slotchange, assignedNodes and shadow-root options explained

Reference for the Shadow DOM slot API — the slot element, named vs default slots, the slotchange event, assignedNodes and assignedElements, ::slotted styling, and attachShadow mode and delegatesFocus options. It runs free in your browser on Gera Tools, with nothing uploaded.

Last updated Source: Gera Tools

What is the difference between a named slot and the default slot?

A <slot> with no name is the default slot and receives any light-DOM child that has no slot attribute. A <slot name='x'> only receives children whose slot='x'. There can be at most one default slot; extra unnamed slots stay empty. Unmatched children are not rendered.

Slots project light-DOM children into a component’s shadow tree. This reference covers the <slot> element, named slots, the slotchange event, the assignedNodes/assignedElements APIs, ::slotted() styling and the attachShadow options.

How it works

A component places <slot> elements in its shadow root. The browser then assigns the host’s light-DOM children into those slots to form the flattened tree that is actually rendered:

<my-card>
  <h2 slot="title">Hello</h2>   <!-- → <slot name="title"> -->
  <p>Body text</p>              <!-- → default <slot> -->
</my-card>

Inside the shadow root:

<div class="head"><slot name="title"></slot></div>
<div class="body"><slot></slot></div>

Children with a matching slot="..." attribute go to the named slot; everything else goes to the single default (unnamed) slot. Unmatched children render nothing. A slot’s own contents act as fallback shown only when no nodes are assigned.

Reacting and styling

  • Listen for slotchange on a slot to know when its assigned nodes change. It fires for assignment changes, not for mutations inside an already-assigned node.
  • Read what is assigned with slot.assignedElements({ flatten: true }) (elements only) or assignedNodes() (includes text/comments).
  • Style top-level projected nodes from the shadow root with ::slotted(selector) — it cannot reach descendants of assigned nodes.
  • attachShadow({ mode }) controls whether element.shadowRoot is exposed (open) or null (closed); delegatesFocus: true forwards focus to the first focusable child.

Common patterns and gotchas

Default content fallback

A slot can carry fallback markup that renders only when no light-DOM node is assigned to it. This is useful for placeholder text or a default icon without requiring the component consumer to provide anything:

<slot name="icon">
  <svg><!-- default icon --></svg>
</slot>

Once any node with slot="icon" is slotted in, the fallback disappears completely.

Detecting slotchange correctly

A common mistake is expecting slotchange to fire when the content inside a slotted element changes — for example, updating the text of an already-assigned <p>. It does not. The event fires only when the set of assigned nodes on the slot changes. To react to deeper mutations you need a MutationObserver on the assigned elements themselves, obtained via assignedElements().

The flatten option

When a custom element nests inside another custom element that also uses slots, a slot can be reassigned through multiple levels of shadow trees. Calling assignedNodes({ flatten: true }) walks that chain and returns the actual light-DOM leaf nodes, rather than the intermediate slot elements. Without flatten: true you get the intermediate slots when slots are composed, which is rarely what you want.

::slotted limitations

The ::slotted() pseudo-element can match only the top-level assigned node — it cannot pierce down into a slotted element’s own children. So ::slotted(p span) will not work; you can write ::slotted(span) and it will match only spans assigned directly to the slot, not spans nested inside a slotted <div>. For deeper styling, use CSS custom properties or parts as a bridge between the shadow and light trees.

open vs closed in practice

mode: 'closed' hides the shadowRoot from element.shadowRoot, but any code that runs inside the element’s class constructor still has the reference. Frameworks and testing libraries that rely on shadowRoot access can be blocked by closed mode, so weigh the friction against the modest privacy it provides before choosing it.

Quick member checklist

MemberWhat it does
<slot>Default slot — receives unmatched light-DOM children
<slot name="x">Named slot — receives children with slot="x"
slotchange eventFires when assigned nodes on the slot change
assignedNodes()Returns assigned nodes including text and comment nodes
assignedElements()Returns only assigned Element nodes
{ flatten: true }Resolve nested slot composition to leaf nodes
::slotted(sel)Style top-level projected nodes from within shadow root
attachShadow({ mode })open exposes .shadowRoot; closed returns null
delegatesFocus: trueForwards focus to first focusable element in the shadow