References

Astro Composition Patterns

Core Patterns

  • Default and Named Slots: Use <slot /> for default content and <slot name="x" /> for multiple named content regions
  • Slot Fallbacks: Provide default content inside <slot> elements when no content is passed
  • Conditional Slots: Use Astro.slots.has('name') to conditionally render slot wrapper elements
  • Layout Composition: Build page layouts with named slots for head, header, sidebar, and footer regions

Slot-based composition in Astro components.


Default and Named Slots

Astro uses <slot> elements instead of React’s children prop.

---
// Card.astro
interface Props {
  variant?: 'default' | 'compact';
}
const { variant = 'default' } = Astro.props;
---

<div class:list={['card', variant]}>
  <div class="card-header">
    <slot name="header" />
  </div>
  <div class="card-body">
    <slot />  <!-- Default slot: unnamed content -->
  </div>
  <div class="card-footer">
    <slot name="footer" />
  </div>
</div>
<!-- Usage -->
<Card>
  <h2 slot="header">Title</h2>
  <p>Default slot content</p>
  <Button slot="footer">Action</Button>
</Card>

Slot Fallback Content

Provide default content when slot is empty.

---
// Panel.astro
---

<div class="panel">
  <header class="panel-header">
    <slot name="header">
      <h2>Default Title</h2>  <!-- Fallback content -->
    </slot>
  </header>
  <div class="panel-body">
    <slot>
      <p>No content provided.</p>  <!-- Fallback content -->
    </slot>
  </div>
</div>

Layout Components with Slots

Page layouts using named slots for regions.

---
// BaseLayout.astro
interface Props {
  title: string;
}
const { title } = Astro.props;
---

<html lang="en">
  <head>
    <title>{title}</title>
    <slot name="head" />  <!-- Extra head content -->
  </head>
  <body>
    <header>
      <slot name="header">
        <nav>Default Navigation</nav>
      </slot>
    </header>

    <aside>
      <slot name="sidebar" />  <!-- Optional sidebar -->
    </aside>

    <main>
      <slot />  <!-- Main page content -->
    </main>

    <footer>
      <slot name="footer">
        <p>Default footer</p>
      </slot>
    </footer>
  </body>
</html>
---
// index.astro
import BaseLayout from '../layouts/BaseLayout.astro';
---

<BaseLayout title="Home">
  <link slot="head" rel="stylesheet" href="/special.css" />
  <CustomNav slot="header" />
  <SideMenu slot="sidebar" />
  <HomeContent />
  <!-- No footer slot = uses default -->
</BaseLayout>

Conditional Slots

Check if a slot has content using Astro.slots.has().

---
// Card.astro
const hasFooter = Astro.slots.has('footer');
---

<div class="card">
  <div class="card-body">
    <slot />
  </div>

  {hasFooter && (
    <div class="card-footer">
      <slot name="footer" />
    </div>
  )}
</div>

Slot with Dynamic Content

Pass data to slots using Astro.slots.render() (advanced).

---
// DataList.astro
interface Props {
  items: string[];
}
const { items } = Astro.props;
---

<ul>
  {items.map(item => (
    <li>
      <slot name="item" />  <!-- Each item slot -->
      {item}
    </li>
  ))}
</ul>

Composition vs Props — Decision Guide

Content varies between uses?
  → Use <slot /> (default slot)

Multiple distinct content areas?
  → Use named slots: <slot name="header" />, <slot name="footer" />

Slot may be empty?
  → Add fallback content: <slot>Default text</slot>

Need to conditionally render slot wrapper?
  → Use Astro.slots.has('slotName') before rendering wrapper

Cross-References