Slots & Composition
Slots recap: injecting markup
Section titled “Slots recap: injecting markup”A component often wraps content it does not know in advance. The markup a parent puts between the component’s tags is rendered wherever <slot /> appears — that is the default slot.
---const { heading } = Astro.props;---<section class="panel"> <h2>{heading}</h2> <slot /></section><Panel heading="Notes"> <p>This paragraph lands in the default slot.</p></Panel>Named slots: multiple insertion points
Section titled “Named slots: multiple insertion points”One default slot is not enough for a real layout. Named slots give you several labeled holes. The component declares <slot name="x" />; the parent fills it with slot="x" on a child:
<header><slot name="header" /></header><main><slot /></main> <!-- default slot --><footer><slot name="footer" /></footer><Shell> <h1 slot="header">Page title</h1> <p>Body content fills the default slot.</p> <small slot="footer">© 2026</small></Shell>Fallback content
Section titled “Fallback content”A <slot> can hold default content that renders only when the parent passes nothing for that slot:
<button class="btn"> <slot>Click me</slot> <!-- "Click me" shows if no children are passed --></button><Button /> <!-- renders: Click me --><Button>Save</Button> <!-- renders: Save -->Checking whether a slot was passed
Section titled “Checking whether a slot was passed”Inside the component you can ask whether a slot received content, via Astro.slots.has, and render structure conditionally:
---const hasAside = Astro.slots.has('aside');---<div class:list={["layout", { "has-aside": hasAside }]}> <main><slot /></main> {hasAside && ( <aside><slot name="aside" /></aside> )}</div>This lets a component adapt its markup — for example, only render the aside wrapper when there is actually an aside to show, so you do not emit an empty element.
Slots build layouts
Section titled “Slots build layouts”This is what slots are for in practice. A layout is a component that defines the page shell — the <html>, <head>, nav, footer — with a <slot /> for the page body. Every page wraps its content in the layout, so the shell lives in one place.
---const { title } = Astro.props;---<html lang="en"> <head> <meta charset="utf-8" /> <title>{title}</title> <slot name="head" /> </head> <body> <nav>…site nav…</nav> <main><slot /></main> <footer>…site footer…</footer> </body></html>---import BaseLayout from '../layouts/BaseLayout.astro';---<BaseLayout title="About"> <link slot="head" rel="canonical" href="https://example.com/about" /> <h1>About us</h1> <p>Page-specific content in the default slot.</p></BaseLayout>flowchart TB layout["BaseLayout: html, head, nav, footer with slots"] --> page["Page fills the default slot with its body"] page --> head["Page fills the head slot with meta tags"] layout --> out["one composed HTML document"] page --> out head --> out
Composition patterns
Section titled “Composition patterns”With slots you compose small components into pages the same way you compose functions: a Card with a default slot for its body, a Grid whose default slot holds cards, a Section with named header and body slots. You keep each piece focused and let the parent decide what goes inside. Because it is all server-rendered, composition costs nothing at runtime — it is just HTML assembly.