Slots in LWC – Default & Named Slots

Slots in LWC – Default & Named Slots

Slots in LWC

Default & Named Slots — Complete Guide with Examples

Learn how to use default slots and named slots in Lightning Web Components to build flexible, reusable child components that accept dynamic content from parent components — a core pattern for composable LWC architecture.

1 Introduction to Slots

A slot is a placeholder inside a child component's template that a parent component can fill with its own HTML content. This is the standard Web Components composition model — LWC implements it natively.

Think of a slot as a "hole" in a component's template. The child component defines where the hole is; the parent component decides what to put in it.

LWC supports two types of slots:

Default slot — A single unnamed slot that catches any content passed without a slot attribute.
Named slot — A slot with a specific name attribute, allowing the parent to target exactly which slot receives which content.

CHILD COMPONENT (defines slots)
Child template structure:
⬜ <slot></slot> ← Default Slot (unnamed)
🟣 <slot name="footer"></slot> ← Named Slot
⬇ Parent passes content into these slots ⬇
PARENT COMPONENT (fills slots)
Parent usage:
🟢 <c-child> <p>Goes into default slot</p> </c-child>
🟢 <c-child> <p slot="footer">Goes into named slot</p> </c-child>
⚠️ Important: Slots only work when a child component is used inside a parent component. The child defines the slots; the parent fills them.

2 Default Slot — Basic Example

A default slot is declared in the child component using a bare <slot></slot> tag with no name attribute. Any content placed inside the child component tag in the parent will flow into this slot.

📌 Syntax (child): <slot></slot> — place this where you want parent content to appear.
Child Component — myCard.html
<template>
    <div class="card-wrapper"
         style="border:1px solid #0070D2; border-radius:10px;
                padding:16px; background:#fff;">

        <h3 style="color:#032D60; margin-bottom:12px;">
            📦 My Card Component
        </h3>

        <!-- Default slot: parent content goes here -->
        <slot></slot>

    </div>
</template>
Parent Component — parentPage.html
<template>
    <lightning-card title="Default Slot Demo" icon-name="utility:insert_template">
        <div class="slds-p-around_medium">

            <c-my-card>
                <!-- This content fills the default slot in c-my-card -->
                <p>Hello! I am content passed from the parent.</p>
                <p style="color:green;">✅ I appear inside the card component.</p>
            </c-my-card>

        </div>
    </lightning-card>
</template>
Child JS — myCard.js
import { LightningElement } from 'lwc';

export default class MyCard extends LightningElement {
    // No special JS needed — slots are purely template-level
}

• The <slot></slot> tag in the child acts as a placeholder — the actual DOM is injected by the parent at runtime.
• The parent passes any HTML elements (paragraphs, buttons, icons, etc.) between the <c-my-card> tags.
• No JavaScript is needed in the child to enable slotting — it is purely a template feature.

3 Default Slot Fallback Content

You can provide fallback content inside a <slot> tag. This fallback is rendered only when the parent passes no content into that slot. It acts as a default value.

Child Component — alertBox.html
<template>
    <div style="border-left:4px solid #0070D2; background:#eff6ff;
                padding:12px 16px; border-radius:0 8px 8px 0;">

        <slot>
            <!-- Fallback: shown only if parent passes no content -->
            <p style="color:#64748b;">
                ℹ️ No custom message provided. This is the default content.
            </p>
        </slot>

    </div>
</template>
Parent — with content (overrides fallback)
<template>
    <div class="slds-p-around_medium">

        <!-- Custom content provided → fallback is NOT shown -->
        <c-alert-box>
            <p>🚨 Critical: Please review the pending approvals immediately!</p>
        </c-alert-box>

        <br/>

        <!-- No content provided → fallback IS shown -->
        <c-alert-box></c-alert-box>

    </div>
</template>

• The first usage passes custom content — the fallback is replaced entirely.
• The second usage passes nothing — the fallback paragraph renders automatically.
• Fallback content is a great pattern for providing a sensible "empty state" in reusable components.

4 Named Slots — Syntax & Basic Example

Named slots allow a child component to define multiple distinct injection points. The parent uses the slot="slotName" attribute on its content elements to target a specific slot.

This is the pattern used to build layout components like cards with separate header, body, and footer regions.

📌 Child syntax: <slot name="header"></slot>
📌 Parent syntax: <p slot="header">Content</p>
Child Component — panelLayout.html
<template>
    <div style="border:1px solid #cbd5e1; border-radius:10px;
                overflow:hidden; font-family:sans-serif;">

        <!-- Named slot: header -->
        <div style="background:#032D60; color:#fff;
                    padding:12px 16px; font-weight:700;">
            <slot name="header">Default Header</slot>
        </div>

        <!-- Named slot: body -->
        <div style="padding:16px; background:#fff;">
            <slot name="body">
                <p style="color:#94a3b8;">No body content provided.</p>
            </slot>
        </div>

        <!-- Named slot: footer -->
        <div style="background:#f8fafc; padding:10px 16px;
                    border-top:1px solid #e2e8f0;">
            <slot name="footer">Default Footer</slot>
        </div>

    </div>
</template>
Parent Component — dashboardPage.html
<template>
    <lightning-card title="Named Slots Demo" icon-name="utility:layout">
        <div class="slds-p-around_medium">

            <c-panel-layout>

                <!-- Targets the "header" named slot -->
                <span slot="header">📊 Q3 Sales Report</span>

                <!-- Targets the "body" named slot -->
                <div slot="body">
                    <p>Total Revenue: <b>$1,250,000</b></p>
                    <p>Units Sold: <b>4,320</b></p>
                    <p>Top Region: <b>North America</b></p>
                </div>

                <!-- Targets the "footer" named slot -->
                <span slot="footer">Last updated: March 2026</span>

            </c-panel-layout>

        </div>
    </lightning-card>
</template>

• The child defines three named slots: header, body, and footer.
• The parent targets each slot using slot="slotName" on the content element.
• Content without a slot attribute goes into the default (unnamed) slot, if one exists.
• Named slots can also have fallback content, shown when the parent provides nothing for that slot.

5 Practical Example — Reusable Modal Component

A classic real-world pattern: a reusable modal dialog where the title, body content, and action buttons are all injected by the parent through named slots. The modal shell is reused across the app — only the content changes.

Child Component — modalShell.html
<template>
    <template lwc:if={isOpen}>

        <!-- Backdrop -->
        <div style="position:fixed; inset:0; background:rgba(0,0,0,0.5);
                    z-index:1000; display:flex; align-items:center;
                    justify-content:center;">

            <!-- Modal Box -->
            <div style="background:#fff; border-radius:12px; width:480px;
                        max-width:90vw; box-shadow:0 20px 60px rgba(0,0,0,0.3);">

                <!-- Modal Header (named slot) -->
                <div style="padding:16px 20px; border-bottom:1px solid #e2e8f0;
                            display:flex; justify-content:space-between;
                            align-items:center;">
                    <h2 style="font-size:18px; color:#032D60;">
                        <slot name="modal-header">Modal Title</slot>
                    </h2>
                    <lightning-button-icon
                        icon-name="utility:close"
                        onclick={closeModal}>
                    </lightning-button-icon>
                </div>

                <!-- Modal Body (named slot) -->
                <div style="padding:20px;">
                    <slot name="modal-body">
                        <p style="color:#94a3b8;">No content provided.</p>
                    </slot>
                </div>

                <!-- Modal Footer (named slot) -->
                <div style="padding:12px 20px; border-top:1px solid #e2e8f0;
                            display:flex; justify-content:flex-end; gap:10px;">
                    <slot name="modal-footer">
                        <lightning-button label="Close" onclick={closeModal}>
                        </lightning-button>
                    </slot>
                </div>

            </div>
        </div>

    </template>
</template>
Child JS — modalShell.js
import { LightningElement, api } from 'lwc';

export default class ModalShell extends LightningElement {
    @api isOpen = false;

    closeModal() {
        this.dispatchEvent(new CustomEvent('close'));
    }
}
Parent Component — contactForm.html
<template>
    <lightning-card title="Contact Form" icon-name="utility:contact">
        <div class="slds-p-around_medium">

            <lightning-button
                label="Open Delete Confirmation"
                onclick={openModal}>
            </lightning-button>

            <c-modal-shell is-open={showModal} onclose={closeModal}>

                <!-- Fill the modal-header slot -->
                <span slot="modal-header">🗑️ Confirm Delete</span>

                <!-- Fill the modal-body slot -->
                <div slot="modal-body">
                    <p>Are you sure you want to delete <b>John Doe</b>?</p>
                    <p style="color:#ef4444; margin-top:8px;">
                        ⚠️ This action cannot be undone.
                    </p>
                </div>

                <!-- Fill the modal-footer slot -->
                <div slot="modal-footer">
                    <lightning-button
                        label="Cancel"
                        onclick={closeModal}
                        class="slds-m-right_small">
                    </lightning-button>
                    <lightning-button
                        label="Delete"
                        variant="destructive"
                        onclick={handleDelete}>
                    </lightning-button>
                </div>

            </c-modal-shell>

        </div>
    </lightning-card>
</template>
Parent JS — contactForm.js
import { LightningElement } from 'lwc';

export default class ContactForm extends LightningElement {
    showModal = false;

    openModal()  { this.showModal = true;  }
    closeModal() { this.showModal = false; }

    handleDelete() {
        // delete logic here
        this.closeModal();
    }
}

• The c-modal-shell component is fully reusable — it knows nothing about the content inside it.
• Each usage of the modal can have completely different header, body, and footer content.
• The onclose custom event bubbles from the child's close button up to the parent to dismiss the modal.
• The @api isOpen property controls visibility from the parent — a clean separation of concerns.

6 Default + Named Slots Together

A child component can have both a default slot and named slots simultaneously. The default slot catches any content that doesn't have a slot attribute.

Child Component — infoPanel.html
<template>
    <div style="border:1px solid #e2e8f0; border-radius:10px; overflow:hidden;">

        <!-- Named slot: icon area on the left -->
        <div style="float:left; padding:16px; background:#f1f5f9;">
            <slot name="icon">
                <span style="font-size:24px;">📋</span>
            </slot>
        </div>

        <!-- Default slot: main text content -->
        <div style="padding:16px; overflow:hidden;">
            <slot>
                <p style="color:#94a3b8;">No content provided.</p>
            </slot>
        </div>

        <!-- Named slot: action button area -->
        <div style="clear:both; padding:10px 16px;
                    border-top:1px solid #e2e8f0; text-align:right;">
            <slot name="action"></slot>
        </div>

    </div>
</template>
Parent Component — notificationPage.html
<template>
    <lightning-card title="Mixed Slots Demo" icon-name="utility:notification">
        <div class="slds-p-around_medium">

            <c-info-panel>

                <!-- Targets named slot "icon" -->
                <span slot="icon" style="font-size:28px;">🔔</span>

                <!-- No slot attribute → goes to the DEFAULT slot -->
                <h4 style="margin:0 0 4px; color:#032D60;">New Assignment</h4>
                <p style="color:#475569;">
                    You have been assigned to the "Salesforce Migration" project.
                </p>

                <!-- Targets named slot "action" -->
                <lightning-button
                    slot="action"
                    label="View Details"
                    variant="brand">
                </lightning-button>

            </c-info-panel>

        </div>
    </lightning-card>
</template>

• The <span slot="icon"> targets the named icon slot on the left.
• The <h4> and <p> have no slot attribute — they go into the default slot automatically.
• The <lightning-button slot="action"> targets the action named slot at the bottom.
• This pattern creates highly composable layout components with zero hard-coded content.

7 Default Slot vs Named Slot — Comparison

Aspect Default Slot Named Slot
Child syntax <slot></slot> <slot name="header"></slot>
Parent targets it via Any content without a slot attribute slot="header" attribute on the element
How many per component Only one default slot allowed Multiple named slots allowed
Fallback content ✅ Supported — content inside <slot> tags ✅ Supported — content inside <slot name="x"> tags
Use case Single content injection point (wrapping components) Multiple distinct regions (header, body, footer)
Typical components Wrappers, containers, alert boxes Modals, cards, panels, page layouts

8 Key Rules & Best Practices

Rule Detail
Child defines, parent fills Only the child component declares <slot> tags. The parent is responsible for providing content to fill them.
Only one default slot A component can have at most one unnamed (default) slot. Use named slots when you need multiple injection points.
Named slots can be empty If the parent doesn't pass content for a named slot, the slot is simply empty (or shows fallback if defined). No error is thrown.
Slot content is styled by parent The slotted content belongs to the parent's light DOM — it is styled by the parent's CSS, not the child's. The child cannot style slot content directly.
Use @api for configuration, slots for content Pass simple values (strings, booleans) via @api properties. Use slots for rich HTML content that varies between usages.
Slots don't add DOM elements The <slot> tag itself is not rendered as a DOM element — it is replaced by the passed content in the final output.
Avoid deeply nested slots Passing content through multiple levels of slot composition (grandparent → parent → child) can make components hard to debug. Keep slot chains shallow.

9 Quick Reference

Default slot (child defines):

<!-- In child component template -->
<template>
    <div class="wrapper">
        <slot></slot>          <!-- default slot, no name -->
    </div>
</template>

Default slot with fallback (child defines):

<slot>
    <p>This renders if parent provides no content.</p>
</slot>

Named slots (child defines):

<template>
    <header><slot name="header">Default Header</slot></header>
    <main>  <slot name="body">Default Body</slot>    </main>
    <footer><slot name="footer">Default Footer</slot></footer>
</template>

Parent filling named slots:

<c-my-component>
    <span  slot="header">My Custom Header</span>
    <div   slot="body">My Custom Body Content</div>
    <p     slot="footer">My Custom Footer</p>
    <span>This goes to the default slot (no slot attr)</span>
</c-my-component>
📝 Remember: The child defines slots with <slot name="x">. The parent fills them with slot="x". Use default slots for single-region wrappers, named slots for multi-region layout components like modals, cards, and panels. Slot content is styled by the parent, not the child.

Popular posts from this blog

Handling Errors in LWC

Hello World using LWC

Lightning Card in LWC