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.
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.
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.
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.
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
Fully supported.
Fully supported.
Fully supported.
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
A caller's content isn't appearing anywhere inside a custom element, despite being placed inside its tags in markup.
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.
A component's internal count/layout logic becomes stale after a caller dynamically adds more slotted children via JavaScript.
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>