Slots

Chapter 8
Slots
Letting a parent pass markup into a child — Vue's content-projection mechanism

Props pass data into a child. Slots pass markup — letting a parent drop arbitrary template content inside a child component. This is Vue's direct equivalent of React's children (Fundamentals Chapter 9) and Angular's <ng-content>, and it's how you build flexible wrappers like cards, modals, and layouts that don't need to know what's placed inside them.

The Default Slot

<!-- Card.vue --> <template> <div class="card"> <slot></slot> </div> </template>
<!-- parent --> <Card> <h2>Hello!</h2> <p>This markup was passed in from outside.</p> </Card>

The <slot></slot> element is a placeholder — whatever the parent writes between the component's opening and closing tags gets rendered there. Card doesn't know or care it's rendering an <h2> and a <p>; it just wraps whatever it's given. This is exactly React's {children}, just declared with a <slot> tag in the template rather than read as a prop.

Fallback (Default) Content

<slot>No content provided.</slot>

Content placed inside the <slot> tag is fallback content — shown only when the parent passes nothing. A small convenience React lacks built-in (you'd write {children ?? 'fallback'} by hand); in Vue it's just whatever you put between the slot tags.

Named Slots — Multiple Insertion Points

<!-- Panel.vue --> <template> <div class="panel"> <header><slot name="header"></slot></header> <main><slot></slot></main> <!-- the default slot --> <footer><slot name="footer"></slot></footer> </div> </template>
<!-- parent: target each slot with #name (v-slot shorthand) --> <Panel> <template #header><h2>Title</h2></template> <p>Main body content (goes in the default slot).</p> <template #footer><small>Footer text</small></template> </Panel>

When a component needs several insertion points, give each <slot> a name. The parent targets a named slot with <template #header> (the # is shorthand for v-slot:header); anything not wrapped in a named template goes into the default slot. This is cleaner than React's approach for multi-region layouts, where you'd pass JSX through several separate named props (the SplitLayout pattern from React Fundamentals Chapter 9) — Vue keeps it all between one set of component tags.

Scoped Slots — Passing Data Back to the Slot Content

<!-- TodoList.vue --> <template> <ul> <li v-for="todo in todos" :key="todo.id"> <slot :todo="todo"></slot> <!-- expose each todo to the parent's slot content --> </li> </ul> </template>
<!-- parent: receive the slot's data via v-slot --> <TodoList :todos="todos"> <template #default="{ todo }"> <strong>{{ todo.text }}</strong> </template> </TodoList>

A scoped slot lets the child pass data up to the markup the parent provides — the child binds values onto its <slot> (:todo="todo"), and the parent receives them by destructuring in v-slot (#default="{ todo }"). This is the powerful case: TodoList owns the looping and data, but the parent decides exactly how each item looks. It's the direct equivalent of React's render-props pattern (Advanced Chapter 5) — "the component owns behavior, the caller controls rendering" — but expressed declaratively in the template.

Slots cover what props and render-props did separately in React
In React you'd use children for simple content, named props for multi-region layouts, and the render-props pattern for "expose data to the caller's markup." Vue's slot system handles all three with one consistent mechanism: default slot, named slots, and scoped slots. If render props felt clunky in React, scoped slots are the cleaner, template-native version of the same idea.
Don't confuse slots (markup) with props (data)
A frequent early mix-up: trying to pass a chunk of markup through a prop, or trying to pass plain data through a slot. The rule is clean — props for values, slots for markup. If you want the parent to control what's rendered, that's a slot; if you want it to control a value the child renders, that's a prop. Scoped slots blur the line slightly (data flows back to slot markup), but the content itself is still markup.
VueReactAngular
<slot> (default){children}<ng-content>
Named slots (#header)Named JSX props<ng-content select="...">
Scoped slotsRender propsStructural directive / template context
Fallback in <slot>Manual children ?? fallbackDefault ng-content content

Coding Challenges

Challenge 1

Build a Card component with a default slot wrapped in a styled div, then use it twice in a parent with completely different inner markup (a heading vs an image + caption). Add fallback slot text shown when no content is passed.

📄 View solution
Challenge 2

Build a Panel component with named header and footer slots plus a default slot for the body, and fill all three from a parent using <template #header> / default content / <template #footer>.

📄 View solution
Challenge 3

Build a List component that takes an items array prop, loops over it, and exposes each item to a scoped slot (:item="item"). In the parent, use v-slot to render each item your own way (e.g. bold name + muted id).

📄 View solution

Chapter 8 Quick Reference

  • <slot></slot> — placeholder for parent-provided markup (= React's children)
  • Content inside the <slot> tag is fallback, shown when the parent passes nothing
  • Named slots (<slot name="header">) — multiple insertion points; parent targets with #header
  • Scoped slots — child binds data onto <slot :x="x">; parent receives via #default="{ x }" (= render props)
  • Rule of thumb: props for values, slots for markup
  • One slot mechanism covers what React split across children, named props, and render props
  • Next chapter: lifecycle hooks — onMounted, onUnmounted, and friends