# Slots and Composition

Course: [Astro](https://www.devtutorials.ai/learn/astro) · Section: Build Pages
Source: https://www.devtutorials.ai/learn/astro/build-pages/slots-and-composition

> The default slot, named slots, fallback content, passing slots to child components, Astro.slots, the class:list, set:html, set:text and is:raw directives, Astro 7's HTML rules, and composing components.

## Slots

- Props pass **values** into a component.
- Sometimes you want to pass **HTML** instead: a paragraph, a list, even other components.
- A **slot** is a hole in a component where the HTML you put inside the tag appears.
- In this lesson, you will build wrapper components with slots and learn Astro's template directives.

**Simple lesson:** props carry values, slots carry HTML.

### A Real-World Example

- Asha wants a coloured "Note" box in many posts.
- Sometimes the box holds one sentence. Sometimes a list. Sometimes a code sample.
- She cannot know the content ahead of time, so a prop is the wrong tool.
- She writes one `Note` component with a `<slot />`. Whatever she puts between `<Note>` and `</Note>` appears inside the box.

Press **▶ Run**. The same `Note` box wraps two different contents.

    <strong>Note</strong>
    <slot />
</aside>

<style>
    .note {
      border-left: 4px solid #7c3aed;
      background: #f5f0ff;
      padding: 8px 12px;
      margin: 12px 0;
    }
</style>
`,
    "src/pages/index.astro": `---
---

<h1>My Astro notes</h1>

    <p>Astro sends HTML first.</p>

    <ul>
      <li>Pages live in src/pages.</li>
      <li>Components live in src/components.</li>
    </ul>
`,
  }}
/>

What happened:

- `<slot />` in `Note.astro` marks where the content goes.
- The first `Note` got a paragraph. The second got a list.
- The box's HTML and style are written once.

### What You Will Learn

In this lesson, you will learn:

1. How the default `<slot />` works.
2. How named slots fill several places in one component.
3. How to show fallback content when a slot is empty.
4. How to check slots with `Astro.slots`.
5. How to pass slots on to a child component.
6. The directives `class:list`, `set:html`, `set:text` and `is:raw`.
7. The HTML rules that Astro 7 checks.

## The Default Slot

- **Default slot** = `<slot />` with no name.
- Everything between a component's opening and closing tags goes there.
- Content with no `slot="..."` attribute goes to the default slot.

<figure className="ast-fig ngc-fig">
<svg viewBox="0 0 640 190" role="img" aria-labelledby="fig-slot" style={{ width: "100%", maxWidth: 640, height: "auto" }}>
  <title id="fig-slot">Animation: a paragraph written between Note tags moves into the slot hole of the Note component.</title>
  <rect x="20" y="40" width="250" height="110" rx="10" style={{ fill: "var(--ngc-blue-soft)", stroke: "var(--ngc-blue)" }} />
  <text x="145" y="64" textAnchor="middle" style={{ fill: "var(--ngc-blue)", fontFamily: "var(--sans)", fontSize: 13, fontWeight: 700 }}>The page</text>
  <g style={{ fontFamily: "var(--mono)", fontSize: 12, fill: "var(--ink)" }}>
    <text x="40" y="92">&lt;Note&gt;</text>
    <text x="40" y="132">&lt;/Note&gt;</text>
  </g>
  <rect x="60" y="99" width="160" height="20" rx="4" style={{ fill: "var(--ngc-orange-soft)", stroke: "var(--ngc-orange)" }}>
    <animate attributeName="x" values="60;60;420;420" keyTimes="0;0.2;0.6;1" dur="5s" repeatCount="indefinite" />
    <animate attributeName="y" values="99;99;104;104" keyTimes="0;0.2;0.6;1" dur="5s" repeatCount="indefinite" />
  </rect>
  <text y="114" style={{ fill: "var(--ngc-orange)", fontFamily: "var(--mono)", fontSize: 11, fontWeight: 700 }}><animate attributeName="x" values="70;70;430;430" keyTimes="0;0.2;0.6;1" dur="5s" repeatCount="indefinite" /><animate attributeName="y" values="114;114;119;119" keyTimes="0;0.2;0.6;1" dur="5s" repeatCount="indefinite" />&lt;p&gt;Astro is fast&lt;/p&gt;</text>
  <rect x="370" y="40" width="250" height="110" rx="10" style={{ fill: "var(--ngc-purple-soft)", stroke: "var(--ngc-purple)" }} />
  <text x="495" y="64" textAnchor="middle" style={{ fill: "var(--ngc-purple)", fontFamily: "var(--sans)", fontSize: 13, fontWeight: 700 }}>Note.astro</text>
  <text x="390" y="88" style={{ fill: "var(--ink)", fontFamily: "var(--mono)", fontSize: 12 }}>&lt;aside&gt;</text>
  <rect x="410" y="98" width="180" height="28" rx="4" style={{ fill: "none", stroke: "var(--ngc-purple)", strokeDasharray: "4 4" }} />
  <text x="500" y="140" textAnchor="middle" style={{ fill: "var(--ngc-purple)", fontFamily: "var(--mono)", fontSize: 11 }}>&lt;slot /&gt; is here</text>
  <text x="320" y="178" textAnchor="middle" style={{ fill: "var(--ngc-slate)", fontFamily: "var(--sans)", fontSize: 13 }}>The content between the tags fills the slot.</text>
</svg>
<figcaption>Whatever you write between the tags lands where the slot is.</figcaption>
</figure>

**Simple lesson:** put `<slot />` where the outside content should appear.

## Named Slots and Fallback Content

### Named Slots

- **Named slot** = `<slot name="footer" />`. A component can have many.
- To fill it, add `slot="footer"` to an element: `<p slot="footer">Thanks!</p>`.
- Everything without a `slot` attribute still goes to the default slot.

### Fallback Content

- **Fallback content** = what a slot shows when nobody fills it.
- Write it between the slot tags: `<slot name="footer">No footer yet.</slot>`.

Press **▶ Run**. The first card fills both slots. The second leaves the footer empty, so it shows the fallback.

    <header>
      <slot name="icon">📄</slot>
      <h2>{title}</h2>
    </header>
    <slot />
    <footer>
      <slot name="footer">No comments yet.</slot>
    </footer>
</article>
`,
    "src/pages/index.astro": `---
---

    <span slot="icon">🧩</span>
    <p>Slots let a component wrap any content.</p>
    <p slot="footer">3 comments</p>

    <p>Nobody has commented on this one.</p>
`,
  }}
/>

- The order of the children does not matter. Each one goes to the slot it names.
- `<Fragment slot="footer">` fills a slot with several elements and no extra wrapper.

**Simple lesson:** `name` on the slot, `slot` on the content. Fallback goes between the slot tags.

## Checking and Passing Slots

### Astro.slots

- `Astro.slots.has("footer")` is `true` when the parent filled the `footer` slot.
- Use it to skip a wrapper that would be empty.
- `await Astro.slots.render("footer")` gives you the slot's HTML as a string. You rarely need it.

### Passing Slots to Child Components

- A component can pass a slot it received on to a component inside it.
- Write `<slot name="footer" slot="footer" />` inside the child's tags.
- Read it as: "take my `footer` slot and put it into the child's `footer` slot".

    <slot />
    {Astro.slots.has("footer") && (
      <footer class="card-footer">
        <slot name="footer" />
      </footer>
    )}
</article>
`,
    "src/components/PostBox.astro": `---
const { title } = Astro.props;
---

    <h2>{title}</h2>
    <slot />
    <slot name="footer" slot="footer" />
`,
    "src/pages/index.astro": `---
---

    <p>This post has a footer.</p>
    <small slot="footer">Posted by Asha</small>

    <p>This post has none, so no empty footer tag is made.</p>
`,
  }}
/>

- Open **HTML sent to browser**. Only the first post has a `<footer>` tag.
- `PostBox` wraps `Card`, and passes its own `footer` slot down to it.

**Simple lesson:** `Astro.slots.has()` checks a slot. `<slot name="x" slot="x" />` passes it on.

## Template Directives

- **Directive** = a special attribute with a colon, such as `class:list`. Astro reads it at build time.

### class:list

- `class:list` builds the `class` attribute from a list.
- Strings are always added. An object adds each key whose value is `true`.
- Example: `class:list={["card", { featured: isTop, draft: !isPublished }]}`.

### set:html

- `set:html` puts a string inside an element **as HTML**.
- Use it for HTML you made or trust, such as a rendered blog post.

### set:text

- `set:text` puts a string inside an element **as plain text**.
- Any `<` or `>` in it is shown as characters, never run as HTML.

### is:raw

- `is:raw` tells Astro not to read `{ }` inside an element.
- Useful when you want to show Astro code as an example.

    <p set:html={trusted} />
    <p set:text={fromVisitor} />
    <code is:raw>{name}</code>
</article>
`,
  }}
/>

What to compare in **HTML sent to browser**:

- **class:** it became `class="card featured draft"`.
- **set:html:** `<em>` is a real tag. The word "Written" is in italics.
- **set:text:** `<b>` is shown as text, so the visitor sees the tags.
- **is:raw:** `{name}` stayed as text.

Never pass text typed by a visitor to `set:html`. If they type a `<script>`, it runs in every reader's browser. This attack is called **cross-site scripting (XSS)**. Normal `{ }` expressions and `set:text` are safe: they show the text without running it.

**Simple lesson:** `class:list` for classes, `set:html` only for trusted HTML, `set:text` for anything else.

## HTML Rules in Astro 7

Astro 7 has a new compiler. It is stricter than earlier versions.

### Unclosed Tags Are Now Errors

- Every tag you open must be closed: `<p>...</p>`, `<div>...</div>`.
- Browsers forgive a missing `</p>`. The Astro 7 compiler does not. It stops the build.
- **Void elements** = tags that never have content, so they need no closing tag: `<img>`, `<br>`, `<hr>`, `<input>`, `<meta>`, `<link>`.

### Valid Tag Nesting

- Some tags cannot hold others. A `<p>` cannot hold a `<div>`.
- Astro 7 keeps your tags as you wrote them. The browser may then move them, and your styles may break.
- Write HTML the browser allows: put a `<div>` beside a `<p>`, not inside it.

### Self-Closing Tags

- Any tag may be written as self-closing: `<div />` becomes `<div></div>`.
- Components with no content are usually written that way: `<Footer />`.

Press **▶ Run**. The build stops. Read the terminal, then close the paragraph and run again.

<p>This paragraph is never closed.
<img src="/favicon.svg" alt="icon" width="32" height="32">
<br>
`,
  }}
/>

- The terminal names the tag and the line.
- `<img>` and `<br>` are fine without a closing tag.

In this lab, the browser runs Astro's earlier compiler, which closes tags silently. So the lab adds the same "never closed" check that Astro 7 makes. The message on your computer looks different, but the rule is the same.

**Simple lesson:** close every tag except void tags. Astro 7 will not guess for you.

## Reusing Components

### Composing with Slots

- Slots let you build "frame" components: cards, boxes, sections, tabs.
- The frame decides the look. The page decides the content.
- You will use the same idea for whole pages in **Layouts and the Document Head**.

### Props or Slots?

Use a **prop** when:

- the value is short text or a number, such as a title or a count,
- the component must change its look from it, such as `tag="Astro"`.

Use a **slot** when:

- the content is HTML, such as paragraphs, lists or other components,
- the content changes a lot from place to place.

Start with props. Switch to a slot when you notice you are passing HTML as a text prop.

**Simple lesson:** values as props, HTML as slots. Many components use both.

## TL;DR

1. **Slot** = `<slot />`, a hole for the HTML between a component's tags.
2. **Named slot** = `<slot name="x" />`, filled by `slot="x"` on an element.
3. **Fallback** = content between the slot tags. Shown when the slot is empty.
4. **Astro.slots.has("x")** = was slot `x` filled?
5. **Pass a slot on** = `<slot name="x" slot="x" />`.
6. **class:list** = classes from strings and true/false objects.
7. **set:html** = trusted HTML only. **set:text** = always plain text.
8. **is:raw** = leave `{ }` as text.
9. **Astro 7 rule** = close every tag, except void tags like `<img>` and `<br>`.

## Common mistakes

<div className="tscroll">

| Mistake | Wrong idea | Correct idea | What goes wrong |
|---|---|---|---|
| No `<slot />` in the component | Children show up by themselves | Add `<slot />` where they should go | The content between the tags disappears |
| `slot="footer"` with no matching slot | Any name works | Use the same name as `<slot name="footer" />` | The content is dropped |
| Visitor text in `set:html` | It is just text | Use `{ }` or `set:text` | XSS: a script runs in readers' browsers |
| Missing `</p>` or `</div>` | The browser fixes it | Close every tag | Astro 7 stops the build |
| `<div>` inside `<p>` | Any tag can hold any tag | Put block tags beside the paragraph | The browser moves tags and styles break |
| An empty wrapper for a missing slot | Empty tags are harmless | Check with `Astro.slots.has()` | Empty boxes with borders appear |

</div>

## Check Yourself

## Exercises

## What to Remember

- `<slot />` is where a component's children go.
- Named slots fill several places. Fallback content fills an empty slot.
- `Astro.slots.has()` checks a slot. `<slot name="x" slot="x" />` passes it on.
- `class:list` builds classes. Use `set:html` only for trusted HTML.
- Close every tag in Astro 7, except void tags.

Next: **Layouts and the Document Head**, where one component frames every page of the site.
