Named Slots
A single default <slot /> is enough for components that wrap one chunk of content, but real layouts usually have several distinct regions — a header, a sidebar, a footer, a set of action buttons. Named slots let a component expose multiple, independently-addressable injection points, and the parent decides which markup lands in each one using the slot attribute. This keeps your layout declarative and your page templates readable, all while preserving Astro’s zero-JavaScript-by-default rendering.
How named slots work
Inside a component you give a slot a name with the name attribute: <slot name="header" />. The consuming page then tags any element with a matching slot="header" attribute, and Astro routes that element’s markup into the named hole during the server render. Anything passed without a slot attribute falls through to the default unnamed <slot />.
Here is a card component that exposes three regions:
---
// src/components/Card.astro
const { title } = Astro.props;
---
<article class="card">
<header class="card__header">
<slot name="title">{title}</slot>
</header>
<div class="card__body">
<slot />
</div>
<footer class="card__footer">
<slot name="actions" />
</footer>
</article>
<style>
.card { border: 1px solid #e2e8f0; border-radius: 12px; overflow: hidden; }
.card__header { padding: 1rem; background: #f8fafc; font-weight: 600; }
.card__body { padding: 1rem; }
.card__footer { padding: 1rem; border-top: 1px solid #e2e8f0; }
</style>
Targeting slots from the parent
The parent fills each named region by setting slot="<name>" on the element it passes in. The order of the elements in the page does not matter — Astro matches them by name, not position.
---
// src/pages/product.astro
import Card from "../components/Card.astro";
---
<Card>
<h2 slot="title">Astro Pro Plan</h2>
<p>Everything you need to ship fast, content-driven sites.</p>
<div slot="actions">
<a href="/signup" class="btn">Subscribe</a>
<a href="/pricing">Compare plans</a>
</div>
</Card>
The <h2> lands in the title slot, the <div> lands in actions, and the <p> — which has no slot attribute — flows into the default <slot /> in the card body.
Tip: The
slotattribute only works on direct children of the component. You cannot reach a named slot from a deeply nested element — wrap the content in a single tagged element instead.
Fallback content
A named slot can declare default markup that renders only when the parent does not supply that slot. Place the fallback between the opening and closing <slot> tags. In the card above, <slot name="title">{title}</slot> falls back to the title prop when no slot="title" element is passed.
<Card title="Untitled">
<p>This card uses the title prop as a fallback.</p>
</Card>
Because no slot="title" element is provided, the header renders “Untitled”. This pattern lets one component serve both quick prop-driven usage and rich markup-driven usage.
Default vs named slots
| Aspect | Default slot | Named slot |
|---|---|---|
| Declaration | <slot /> | <slot name="header" /> |
| Targeted by | Any untagged child | slot="header" on a child |
| Count per component | One | Many (unique names) |
| Fallback support | Yes | Yes |
| Typical use | Main content body | Header, footer, sidebar, actions |
Warning: Slot names must be unique within a component. If two
<slot name="x" />declarations share a name, both render the same passed content, which is almost never what you want.
Forwarding slots with <slot name> and Astro.slots
For wrapper or layout components you sometimes need to know whether a slot was filled, or render it programmatically. The Astro.slots API exposes has() and render() for exactly this:
---
// src/components/Panel.astro
const hasAside = Astro.slots.has("aside");
const asideHtml = hasAside ? await Astro.slots.render("aside") : "";
---
<div class:list={["panel", { "panel--with-aside": hasAside }]}>
<main><slot /></main>
{hasAside && <aside set:html={asideHtml} />}
</div>
This is purely a server-side operation — the conditional <aside> simply never reaches the browser when unused, so there is no client cost and no hydration. It pairs well with islands: a slotted, interactive child can still carry its own client:visible directive while everything around it stays static HTML.
<Panel>
<p>Documentation body.</p>
<nav slot="aside">
<TableOfContents client:idle />
</nav>
</Panel>
Best practices
- Reserve the default slot for the primary content and use named slots for supporting regions like headers, footers, and sidebars.
- Give slots descriptive, role-based names (
actions,meta,aside) rather than positional ones (top,bottom). - Provide fallback content for optional regions so the component renders sensibly when a slot is omitted.
- Use
Astro.slots.has()to conditionally render wrapper markup and avoid empty containers in the output HTML. - Keep slotted children as single wrapper elements — the
slotattribute only applies to direct children of the component. - Let interactive islands live inside slots and attach
client:*directives to them so the surrounding layout stays zero-JS.