LEARNING OBJECTIVES ⌵
- Implement declarative fallback content inside named and default
<slot>elements. - Understand the browser's exact condition for rendering fallback markup vs projected content.
- Diagnose and eliminate the "whitespace text node" gotcha that unintentionally suppresses fallback content.
- Build resilient UI components that gracefully degrade when consumers omit optional slot markup.
📖 The Mental Model & Story (Intuitive Foundation)
Imagine a high-end coffee shop with digital order confirmation kiosks. At the bottom of every receipt screen is an advertising banner space.
The kiosk software is programmed with a simple, robust rule:
- Rule 1 (Partner Sponsor Present): If a local bakery pays for a promotional banner, display their custom graphic and coupon code.
- Rule 2 (No Sponsor Present): If no external promotion is configured for the day, automatically fall back to the cafe’s default house slogan: "Thank you for supporting your local roastery!"
+-------------------------------------------------------------------------------+
| FALLBACK SLOT SWITCHING PIPELINE |
+-------------------------------------------------------------------------------+
| |
| SCENARIO A: Consumer supplies Light DOM content |
| <user-avatar> |
| <img slot="avatar" src="ceo.jpg" /> ───┐ |
| </user-avatar> │ |
| v (Assigned!) |
| Shadow DOM: <slot name="avatar"> <svg>DEFAULT ICON</svg> </slot> |
| Render Output: [ Shows ceo.jpg ] (SVG Fallback is suppressed) |
| |
|-------------------------------------------------------------------------------|
| |
| SCENARIO B: Consumer provides NO matching content |
| <user-avatar></user-avatar> │ (Nothing assigned!) |
| v |
| Shadow DOM: <slot name="avatar"> <svg>DEFAULT ICON</svg> </slot> |
| Render Output: [ Shows DEFAULT SVG ICON ] (Fallback activated!) |
| |
+-------------------------------------------------------------------------------+
The <slot> element supports this exact declarative fallback behavior natively. Any markup placed inside the <slot> element itself in the Shadow DOM serves as the default fallback content.
Technical Deep Dive & Specifications
Fallback Content Syntax
To declare fallback content, simply place HTML elements or text nodes directly between the opening <slot> and closing </slot> tags inside your Shadow DOM template:
<!-- Inside Shadow Root Template -->
<div class="user-badge">
<!-- Named slot with fallback SVG icon -->
<slot name="icon">
<svg class="fallback-icon" viewBox="0 0 24 24">
<circle cx="12" cy="12" r="10" fill="#64748b"/>
</svg>
</slot>
<!-- Default slot with fallback text -->
<slot>
<span class="fallback-label">Anonymous User</span>
</slot>
</div>
The Fallback Activation Rules (WHATWG Spec)
| Light DOM State | Assigned Nodes Count | Rendered Output |
|---|---|---|
Consumer provides matching element (e.g. <span slot="icon">🔥</span>) |
1 |
Renders the projected consumer element (🔥). Fallback SVG is suppressed. |
Consumer tag is completely empty (<user-badge></user-badge>) |
0 |
Renders the fallback content (Anonymous User / SVG). |
Consumer provides empty tag (<user-badge><span></span></user-badge>) |
1 |
Renders the empty <span>. Fallback is suppressed! |
Consumer includes newline/whitespace in Light DOM (<user-badge>\n </user-badge>) |
1 (Text Node) |
The text node containing whitespace is assigned to the default slot, suppressing the fallback text! |
[ Check Slot Assignment ]
│
┌──────────────────────┴──────────────────────┐
│ │
Assigned Nodes > 0 Assigned Nodes == 0
│ │
v v
Render Light DOM Content Render Internal Fallback Nodes
(Internal fallback hidden) (Declared inside <slot>...</slot>)
The Dreaded "Whitespace Text Node" Gotcha
A very common bug in Web Components occurs when formatting HTML with indentation:
<!-- ❌ BUG: The indentation creates a Text Node with spaces and newlines -->
<user-badge>
</user-badge>
Because the newline and spaces between <user-badge> and </user-badge> form a valid DOM Text node, the browser assigns that whitespace text node to the default <slot>. Since the assigned node count is 1, the fallback content is suppressed, resulting in a blank visual space!
To fix this:
- Ensure self-closing or compact tags when empty:
<user-badge></user-badge>. - Or use named slots (whitespace text nodes without a
slot=""attribute are only assigned to default slots, never named slots).
💻 Interactive Code Playground
Starter Code
Line-by-Line Code Breakdown
- Line 18–22 (
<action-button ...>): Provides all 3 named slots (icon,label,shortcut). Every slot renders custom consumer markup; all fallbacks are suppressed. - Line 25–27 (
<action-button ...>): Provides onlyslot="label". Theiconslot falls back to⚡and theshortcutslot falls back to↵ Enter. - Line 30 (
<action-button variant="danger"></action-button>): Closed cleanly with no internal content. All three slots (icon,label,shortcut) activate their declarative fallback markup. - Line 77–88 (
<slot name="...">...</slot>): Encapsulated fallback markup declared inside the Shadow DOM template.
Expected Browser Render Output
Smart Action Buttons (Fallback States)
[ 🚀 Deploy Cluster Ctrl+D ]
[ ⚡ Sync Repository ↵ Enter ]
[ ⚡ Execute Action ↵ Enter ]🏋️ Hands-On Exercise
🎯 The Challenge: Build an <avatar-badge> with Multi-Tier Fallbacks
Instructions:
- Create an
<avatar-badge>custom element with an open Shadow Root. - In the Shadow DOM, provide a circular container (
width: 64px; height: 64px; border-radius: 50%). - Inside the container, place a
<slot name="image">with an SVG silhouette as fallback content. - Below the avatar, place a
<slot name="status">with a fallback online indicator (🟢 Active Now). - In your demo page, instantiate 3 avatars:
- Avatar 1: Custom image (
<img slot="image" ...>) and custom status (<span slot="status">Busy 🔴</span>). - Avatar 2: Custom image only (status uses fallback).
- Avatar 3: No slots provided (both image and status fall back).
- Avatar 1: Custom image (
🏁 Starter Code Sandbox
⚠️ Common Pitfalls
- Accidental Whitespace in Default Slot: Leaving blank lines or indentation inside
<my-tag> \n </my-tag>creates aTextNodethat prevents default slot fallbacks from rendering. - Assuming Fallback Nodes Exist in Light DOM: Fallback nodes exist exclusively within the Shadow DOM sub-tree. Calling
hostElement.querySelector('.fallback-svg')will returnnull. - Applying
::slotted()to Fallback Content:::slotted()ONLY applies to nodes projected from Light DOM. It does not style internal fallback nodes declared inside<slot>...</slot>. Style fallback nodes with standard shadow DOM CSS class selectors!
💡 Pro Tips
- Zero-JavaScript Placeholders: Native fallback slot content renders instantly with zero JavaScript execution overhead, eliminating layout shift and skeleton loader flickers.
- Accessibility Fallback Labels: Always provide accessible fallback text (e.g.
aria-label="No data available") inside fallback nodes so screen readers announce meaningful context when consumer data is absent.
📌 Key Takeaways
- Fallback markup is declared directly between
<slot>and</slot>inside the Shadow Root. - Fallback content is rendered only when zero matching nodes are assigned to the slot.
- Providing even an empty Light DOM element or whitespace text node suppresses fallback rendering.
::slotted()does not style fallback content; use regular Shadow DOM CSS selectors for fallbacks.- Named slots protect against accidental whitespace suppression because unannotated text nodes do not match named slots.
- --