Svelte slots allow components to accept and render child content, enabling flexible UI composition. By using the
<slot> element, you can define insertion points where parent components inject markup, supporting both default fallbacks and named slots for complex layouts.
Core Concept of Slots
In Svelte, a slot is an HTML element that acts as a placeholder for content defined by the component's parent. This mechanism decouples the structure of a component from its specific content, promoting reusability. When a parent component includes markup inside a child component’s tags, that markup is projected into the nearest available slot within the child.
Consider a simple card component. The shell of the card remains static, but the header and body can vary. Without slots, you would need separate components for every variation. With slots, one generic Card component handles all variations by accepting different children.
<!-- Parent.svelte -->
<Card>
<h2>My Title</h2>
<p>This is the content.</p>
</Card>
<!-- Card.svelte -->
<div class="card">
<header>
<slot />
</header>
<div class="body">
<slot name="body" />
</div>
</div>
In this example, the
<h2> and
<p> tags from the parent are projected into the default slot in the header. If no content is provided for the "body" slot, it remains empty unless a fallback is defined. This approach mirrors the shadow DOM concept but is implemented directly in Svelte's compilation step.
Default Slots and Fallback Content
The most common use case involves a single default slot. If the parent does not provide any content for the slot, the slot itself renders nothing. However, Svelte allows you to define fallback content inside the
<slot> tag. This content is displayed only when the parent fails to provide custom markup for that specific slot.
This feature is particularly useful for optional parameters. For instance, a Button component might have an icon slot. If the user doesn't specify an icon, a default icon should appear.
<!-- Button.svelte -->
<button class="btn">
<span class="icon">
<slot name="icon">
<svg></svg>
</slot>
</span>
<slot />
</button>
Here, if the parent writes
<Button>Click Me</Button>, the SVG appears. If it writes
<Button icon={<CustomIcon />}>Click Me</Button>, the custom icon replaces the SVG. This pattern reduces prop drilling and keeps the component API clean.
For more on handling side effects in Svelte, see
Equivalent of React's useLayoutEffect in Svelte. Understanding how layout interacts with dynamic content helps when slots change the DOM structure unexpectedly.
Named Slots for Complex Layouts
When a component needs multiple insertion points, named slots become essential. You assign a
name attribute to each
<slot> element in the child component. In the parent, you wrap the content intended for a specific slot in a
<svelte:fragment> with a matching
slot attribute.
This technique is standard in building modals, dialogs, or dashboard widgets where distinct areas like headers, footers, and main bodies need independent styling and logic.
<!-- Modal.svelte -->
<div class="modal-overlay">
<div class="modal-content">
<header>
<slot name="header" />
</header>
<main>
<slot />
</main>
<footer>
<slot name="footer" />
</footer>
</div>
</div>
<!-- App.svelte -->
<Modal>
<svelte:fragment slot="header">
<h1>Confirm Action</h1>
</svelte:fragment>
<p>Are you sure you want to proceed?</p>
<svelte:fragment slot="footer">
<button>Cancel</button>
<button>Confirm</button>
</svelte:fragment>
</Modal>
The
<svelte:fragment> wrapper is necessary because HTML does not support multiple root elements in some contexts, and Svelte uses it to identify which fragment belongs to which named slot. It ensures that the compiler correctly maps the parent's markup to the child's slot definitions.
Scoped Slots for Data Passing
While basic slots project raw HTML, scoped slots allow the child component to pass data back to the parent. This is achieved by binding variables to the
<slot> element. The parent can then access these variables using a special syntax within the
<svelte:fragment>.
This pattern is powerful for creating reusable list components or tables where the cell rendering logic depends on data provided by the child.
<!-- List.svelte -->
<ul>
<li each={items}>
<slot item={item} />
</li>
</ul>
<!-- Parent.svelte -->
<List items={myData}>
<svelte:fragment let:item>
<strong>{item.name}</strong> - {item.value}
</svelte:fragment>
</List>
The
let:item directive destructures the props passed from the slot. This creates a tight coupling between the child's data structure and the parent's presentation logic, offering maximum flexibility without sacrificing type safety or reactivity.
Frequently Asked Questions
Can I nest slots inside other components?
Yes, slots can be nested indefinitely. A child component can contain another component that also uses slots. The projection happens based on the scope of the parent that defines the content, ensuring that the correct markup ends up in the right place.
How do I handle conditional content in slots?
You can use standard Svelte
{#if} blocks inside the parent component before passing content to the slot. Alternatively, you can define fallback content directly inside the
<slot> tag in the child component to show default values when the condition in the parent evaluates to false.
What is the difference between a default slot and a named slot?
A default slot captures all content that does not have a specific
slot attribute assigned to it. Named slots require explicit assignment via the
slot attribute in the parent. Only one default slot can exist per component, but there is no limit to the number of named slots.
Do slots work with TypeScript in Svelte?
Yes, Svelte supports TypeScript. You can define types for scoped slot props using JSDoc comments or TypeScript interfaces. This ensures that when you destructure slot variables in the parent, you get proper autocomplete and type checking for the data being passed down.
Why am I seeing duplicate content when using slots?
This usually happens if you accidentally include the same markup in both the default slot and a named slot, or if you render the slot multiple times in the child component. Ensure that each piece of content is assigned to exactly one slot definition to avoid duplication.