šŸš€ LEVEL UP TO SENIOR:Unlock 500+ Advanced Practical Challenges & Exercises.
šŸŽ“ COURSERA PARTNER:Earn professional Google, Meta, and IBM certificates to supercharge your resume.
HTML MASTER CLASS /// LEARN TAGS /// BUILD STRUCTURE /// SEMANTIC WEB /// HTML MASTER CLASS /// LEARN TAGS ///

<slot>: The Contract Between A Component And Its Caller

Master the <slot> element: how light-DOM content gets projected into shadow-tree slots, named vs default slots, fallback content, and reacting to dynamic content changes with the slotchange event.

⚔ Total XP: 0|šŸ’» html XP: 0

Skill Matrix

UNLOCK NODES BY LEARNING NEW TAGS.

<slot> Element

Content projection.


šŸš€ LEVEL UP TO SENIOR:Unlock 500+ Advanced Practical Challenges & Exercises.
šŸŽ“ COURSERA PARTNER:Earn professional Google, Meta, and IBM certificates to supercharge your resume.

A genuinely reusable component needs to own its internal structure and styling while still accepting arbitrary content from whoever uses it. <slot>, working alongside Shadow DOM, is the native mechanism defining exactly that contract.

1Projection, Not Relocation

When a custom element's Shadow DOM contains a <slot>, the caller's 'light DOM' children — the markup placed directly inside the custom element's tags in the ordinary page — get rendered at that <slot>'s position within the shadow tree. Critically, this is a rendering-time *projection*, not a DOM mutation: the light-DOM node structurally remains exactly where the caller placed it in the actual document tree, and simply appears, visually, inside the component's internal structure.

This distinction matters practically: event listeners, CSS class targeting, and DOM queries against the light-DOM node from the caller's own code continue to work exactly as if slotting hadn't happened, since nothing about the node's actual position in the document was altered — only where it's painted changed.

<user-card><span slot="name">Ada Lovelace</span></user-card>
<!-- shadow root: -->
<slot name="name"></slot>
localhost:3000
āœ“ Rendered At The Slot, Structurally UnmovedSlotting is purely a rendering-time projection — the caller's DOM node never actually relocates.

2Named Slots For Multiple Insertion Points, Default For Everything Else

A component with multiple distinct content areas — a header, a body, an actions row — declares multiple named slots (<slot name="header">, <slot name="actions">), and the caller routes specific content to each by adding a matching slot="header" (etc.) attribute to their light-DOM children. This is the mechanism that lets a single component expose several independently-addressable content regions, each stylable and positioned distinctly inside the component's own internal layout.

A bare <slot> with no name attribute is the *default slot* — it catches any light-DOM child with no slot attribute at all (or whose value doesn't match any named slot present), acting as the general-purpose fallback destination for content the caller didn't explicitly route anywhere specific.

<product-card>
  <span slot="title">Wireless Mouse</span>
  <p>General description text, goes to default slot</p>
</product-card>
localhost:3000
āœ“ Multiple Named Regions, Plus A Catch-AllNamed slots for specific content areas, the default slot for everything else.

3Fallback Content: A Genuinely Conditional Default

Content authored directly as children of a <slot> element inside the shadow root acts as fallback content, rendering only when the caller supplies nothing matching that slot. This is a real, conditional behavior, not a layering effect — the moment a caller does provide content for that slot, the fallback is entirely replaced, never rendered alongside the real content.

This makes fallback content the correct native mechanism for sensible component defaults — a placeholder avatar image, a default 'Untitled' label — without requiring the component's internal JavaScript to conditionally check whether the caller supplied that content and manually render a default otherwise.

<slot name="avatar">
  <img src="default-avatar.png" alt="">
</slot>
<!-- Renders only if caller supplies no slot="avatar" content -->
localhost:3000
āœ“ A Native, Declarative Default — No JS Conditional NeededFallback content handles the 'caller didn't supply this' case entirely in markup.

4Reacting To Dynamic Content With slotchange

Since slotted content can change after a component's initial render — a caller's own JavaScript adding, removing, or reassigning light-DOM children dynamically — the slotchange event fires on the relevant <slot> whenever its set of assigned nodes actually changes, giving the component's own internal code a reliable hook to react: recomputing a count, re-running layout logic, or updating internal state that depends on what content is currently projected.

event.target.assignedElements() (or assignedNodes() to include text nodes) reads the slot's currently assigned content directly, the standard way for a component's internals to inspect what's actually been slotted in at any given moment, rather than assuming it never changes after construction.

slotEl.addEventListener("slotchange", (e) => {
  const items = e.target.assignedElements();
  updateCount(items.length);
});
localhost:3000
āœ“ A Reliable Hook For Post-Render Content Changesslotchange keeps a component correctly reactive to caller-driven content updates, not just its initial state.

5Step-by-Step Breakdown

Letting Callers Fill In The Blanks. A well-designed reusable component needs internal structure it controls (styling, layout) while still accepting content the caller provides. <slot> is the native mechanism that lets a Shadow DOM component declare exactly where that caller-provided content gets projected.

Light DOM Children Get Projected Into A Component's <slot>. When a custom element has a shadow root containing a <slot>, any 'light DOM' children the caller places inside the custom element's tags (in normal page markup) are rendered — projected — at that <slot>'s position inside the shadow tree, without actually moving in the underlying DOM.

How Slotting Works. When light DOM content is projected into a <slot>, does it physically move into the shadow tree in the underlying DOM structure?

  • →No — it's rendered at the slot's position, but remains in its original light-DOM location structurally
  • →Yes, it's physically relocated into the shadow tree
  • →A full copy is created and inserted into the shadow tree

Named Slots Route Specific Content To Specific Positions. A named slot (<slot name="name">) only accepts light-DOM children carrying a matching slot="name" attribute, letting a component define multiple distinct insertion points — a header slot, a body slot, an actions slot — each independently addressable by the caller.

Named Slots. How does a <slot name="role"> know which light-DOM children to accept?

  • →Only children with a matching slot="role" attribute are routed there
  • →It always takes the first available light-DOM child, regardless of any attribute
  • →It matches based on a shared CSS class name

The Default (Unnamed) Slot Catches Everything Else. A bare <slot> with no name attribute is the default slot, catching any light-DOM children that either have no slot attribute at all, or don't match any named slot present in the component — the fallback catch-all position for general content.

The Default Slot. A light-DOM child has no slot attribute at all. Where does it get projected, assuming a bare <slot> exists in the shadow root?

  • →The default (unnamed) <slot>
  • →It isn't rendered anywhere at all
  • →The first named slot found in the shadow root

Fallback Content Renders Only When A Slot Is Empty. Content placed directly inside a <slot> element in the shadow root acts as fallback/default content, rendering only when the caller provides no matching light-DOM content for that slot — the moment the caller does supply content, the fallback is replaced entirely, never shown alongside it.

Slot Fallback Content. When does the fallback content placed inside a <slot> element actually render?

  • →Only when the caller provides no matching light-DOM content for that slot
  • →Always, alongside any content the caller does provide
  • →It's purely documentation and never actually renders

slotchange Fires When Assigned Content Changes. The slotchange event fires on a <slot> element whenever the set of light-DOM nodes assigned to it changes — nodes added, removed, or reassigned — the correct hook for a component to react programmatically to caller-supplied content changing after initial render, rather than only at construction time.

The slotchange Event. When does a <slot>'s slotchange event fire?

  • →Whenever the set of light-DOM nodes assigned to that slot changes
  • →On every browser render frame, continuously
  • →Only when CSS styles targeting the slot change

<slot> Mastered. You now know how <slot> projects light-DOM content into a Shadow DOM component's structure, how named and default slots route different content to different positions, how fallback content works, and how to react to content changes with slotchange.

Define A Named Slot. A named <slot> marks where a web component's light-DOM content should be projected.

Level Up šŸš€

Advanced cheat sheets, SEO tricks, and interview prep for this topic.

Browser Support

ChromeSupported

Fully supported.

FirefoxSupported

Fully supported.

SafariSupported

Fully supported.

EdgeSupported

Fully supported.

Accessibility (A11y)

1Slotted Content Retains Its Own Semantics And Must Still Be Correctly Structured By The Caller

Since slotting doesn't alter the actual DOM structure or accessible-tree position of the light-DOM content in any deep sense, the caller remains responsible for supplying appropriately-labeled, semantic markup into each slot.

SEO Implications

  • 1

    Light DOM (Slotted) Content Remains Directly Crawlable, Unlike Shadow DOM Internals In Some Contexts

    Since slotted content structurally lives in the regular light DOM, it's straightforwardly part of the page's crawlable content — a meaningful reason to keep genuinely important page content in the light DOM, letting components merely provide presentation via slotting.

Best Practices

Design Named Slots Around Clear, Stable Content Categories, Not Implementation Detail

A slot named "header" or "actions" documents a component's intended usage contract far better than one named after an internal CSS class or layout detail that might change.

Use Fallback Content For Genuinely Sensible Defaults, Not As A Substitute For Required Content Validation

Fallback content is appropriate for a default avatar or placeholder; for content that's actually required for the component to make sense, validate its presence explicitly rather than silently falling back.

Frequent Bugs

THE BUG

A caller's content isn't appearing anywhere inside a custom element, despite being placed inside its tags in markup.

THE FIX

Check that a matching <slot> (named or default) actually exists in the component's shadow root — without one, light-DOM children with no matching slot simply aren't rendered at all.

THE BUG

A component's internal count/layout logic becomes stale after a caller dynamically adds more slotted children via JavaScript.

THE FIX

Listen for the slotchange event on the relevant slot and recompute using assignedElements(), rather than assuming slotted content is static after initial render.

Real-World Examples

A Card Component With Named Slots And A Default Avatar

A reusable user-card component with distinct named slots for name and role, a default slot for extra content, and fallback avatar content.

<!-- Caller markup -->
<user-card>
  <span slot="name">Ada Lovelace</span>
  <span slot="role">Mathematician</span>
</user-card>

<!-- Component's shadow root -->
<div class="card">
  <slot name="avatar"><img src="default-avatar.png" alt=""></slot>
  <slot name="name"></slot>
  <slot name="role"></slot>
  <slot></slot>
</div>

Interview Prep

?Frequently Asked Questions

Pascual Vila

Pascual Vila

Frontend Instructor // Code Syllabus

Common Pitfalls & Errors

The Error //

Expecting light-DOM content to render inside a custom element with no matching <slot> present in its shadow root

// Shadow root must include: <slot></slot> <!-- or a matching named slot -->

The Solution //

Add a <slot> (named or default) matching the content's slot attribute (or lack thereof) inside the component's shadow root.

The Error //

Assuming slotted content is static and never re-checking it after initial render

slotEl.addEventListener("slotchange", () => { const current = slotEl.assignedElements(); });

The Solution //

Listen for the slotchange event and use assignedElements() to react to content added/removed after the component's initial construction.

Lesson Glossary

[01]<slot>

A shadow-tree insertion point for projected light-DOM content.

Code Preview
<slot name="title"></slot>

[02]Light DOM

A custom element's caller-supplied children, in the ordinary document tree.

Code Preview
<user-card><span slot="name">…</span></user-card>

[03]Named Slot

A slot accepting only children with a matching slot attribute.

Code Preview
slot="name" routes to <slot name="name">

[04]slotchange

Fires on a slot when its assigned light-DOM nodes change.

Code Preview
slot.addEventListener("slotchange", handler)

Continue Learning