Vue.js: Slots in Vue 3

Last updated: 2026-10-20

Slots are Vue's content distribution API — a parent component can insert arbitrary content into a child component's "slot". Slots make a component as flexible as a "template": the parent decides what is shown, the child decides where it is shown.

Vue 3 provides 3 kinds of slots: default slots, named slots, and scoped slots. This lesson covers all three and the best practices around them.

Related Template & Helpers:useSlots()

1. What You'll Learn


2. The Challenge of a Card Component That Cannot Be Customized

(1) Pain Point: 5 Card Styles Means 5 Components

Alice's e-commerce admin needs 5 kinds of cards:

VUE
<!-- ❌ Broken version: 5 components -->
<!-- ProductCard.vue -->
<div class="card">{{ product.name }}</div>

<!-- UserCard.vue -->
<div class="card">{{ user.name }}</div>

<!-- OrderCard.vue -->
<div class="card">Order #{{ order.id }}</div>

<!-- 5 components, 90% identical code, only the content differs -->

Product manager Charlie:

"Alice, next week we're adding 5 more card types. We can't keep adding new components. We need a flexible Card that accepts any content."

(2) The Vue Slot Solution: 1 BaseCard, the Parent Inserts Content

1 BaseCard.vue → unlimited card types. Each new card only needs content; the component never changes.

(3) Benefits

After adopting slots:


3. Default Slots

(1) Basic Usage

Rendered result:

HTML
<div class="card">
  <h3>Hello</h3>
  <p>This is the card content</p>
</div>

(2) Default Content (fallback)

VUE
<!-- The parent passes no content -->
<BaseCard />

<!-- Renders: <p>No content provided</p> -->

<!-- The parent passes content -->
<BaseCard>
  <p>Custom content</p>
</BaseCard>

<!-- Renders: <p>Custom content</p> (overrides the default) -->

(3) 5 Use Cases

Scenario Usage
Card content <slot />
Button label <slot />
List item <slot :item="item" />
Form field <slot />
Modal body <slot />

4. Named Slots

(1) Defining Named Slots

(2) Using Them in the Parent (3 Syntaxes)

(3) Dynamic Slot Names

(4) Detecting Whether a Slot Exists


5. Scoped Slots

(1) Core Idea: the Child Passes Data to the Parent's Slot

Rendered result:

HTML
<ul>
  <li><p>1. Alice (alice@example.com)</p></li>
  <li><p>2. Bob (bob@example.com)</p></li>
  <li><p>3. Charlie (charlie@example.com)</p></li>
</ul>

(2) 5 Scoped Slot Patterns

Pattern Parent syntax Purpose
Receive all props <template #default="slotProps"> Receive the whole object
Destructure <template #default="{ user }"> Use only the fields you need
Rename <template #default="{ user: u }"> Avoid variable clashes
Default values <template #default="{ user = defaultUser }"> Provide a default while destructuring
No template {{ slotProps.user.name }} Simple cases (default slot)

(3) Complete Example: A Customizable List

▶ Example: Scoped Slots (Custom DataTable Columns)

▶ Expand runnable example (71 lines)
▶ Try it Yourself

Output:

TEXT
A 5-column table (ID / Name / Email / Status / Actions) with two rows.
The status column renders coloured badges — green active for Alice, grey inactive for Bob — and the actions column renders an Edit button per row, while ID / Name / Email fall back to raw values because the parent defines no slot for them.
TEXT
ID | Name  | Email        | Status        | Actions
1  | Alice | alice@x.com  | [active]      | [Edit]
2  | Bob   | bob@x.com    | [inactive]    | [Edit]

(1) 📊 5 BaseCard Usage Patterns (Reference Only)

Pattern Parent template Purpose
1. Simplest (body only) <BaseCard><p>Simple</p></BaseCard> Default slot only
2. With a header <BaseCard><template #header>...</template>...</BaseCard> Add a header
3. Full header + body + footer three <template #name> blocks Full layout
4. Explicit form (v-slot) <template v-slot:header> Long form (legacy docs)
5. Scoped slot <template #actions="{ user }"> Child → parent data

(2) 📊 5 Common Mistakes (Reference Only)

Mistake Symptom Fix
Misspelled slot name Content does not show Make <slot name="..."> and #name match
Multiple root elements in the parent Warning Wrap them in <template #name>
Legacy v-slot syntax Vue 2 compatibility Switch to v-slot:header or #header
Default slot never passed Nothing shows Check the content inside <Component />
Not destructuring a scoped slot Verbose templates Destructure with { user, index }

(3) 📊 5 Performance Comparisons

Syntax Compilation Performance Recommendation
<slot /> Static ⭐⭐⭐⭐⭐ Default
<slot name="x" /> Static ⭐⭐⭐⭐⭐ Named
<slot :x="x" /> Static ⭐⭐⭐⭐ Scoped
v-if="$slots.x" Conditional ⭐⭐⭐⭐ Detect existence
useSlots() Runtime ⭐⭐⭐ Used in JS

6. $slots and useSlots

(1) Accessing Slots with $slots

(2) useSlots (Composition API)

VUE
<script setup>
const { useSlots } = Vue

const slots = useSlots()

// Check whether a slot exists
if (slots.header) {
  console.log('Header slot exists')
}

// Render dynamically
if (slots.default) {
  console.log('Default slot exists')
}
</script>

(3) $slots vs useSlots

Dimension $slots useSlots()
API type Used directly in templates Used in JS (Composition API)
Returns The slots object The slots object (in refs form)
Where Inside template Inside <script setup>

▶ Example: 5 Uses of $slots

▶ Try it Yourself

Output:

TEXT
A bordered wrapper with three stacked areas — a grey Header content strip on top, the default content Default slot: main content in the middle, and a grey Footer content strip at the bottom — followed by a small grey line Detected slots: header, default, footer.
Because all three slots were passed, the Default fallback text never renders;
if you delete the <template #header>, that strip and the header key both disappear.
TEXT
┌──────────────────────────────┐
│ Header content               │
│ Default slot: main content   │
│ Footer content               │
│ Detected slots: header, default, footer │
└──────────────────────────────┘

7. Complete Example: A Customizable Card Component

▶ Example: BaseCard with Default + Named Slots

▶ Try it Yourself

Output:

TEXT
Three stacked cards built from the same component.
The first shows only a body with Simple content (only body);
the second adds a grey header strip reading Product Name;
the third has a header (Order #12345), a body (Total: $99.99) and a footer with a green View Details button.
Because each card passes different slots, no No content (default) fallback ever appears.
TEXT
┌─────────────────────────────┐
│ Simple content (only body)  │
└─────────────────────────────┘
┌─────────────────────────────┐
│ Product Name      (header)  │
│ Product description         │
└─────────────────────────────┘
┌─────────────────────────────┐
│ Order #12345      (header)  │
│ Total: $99.99               │
│ [View Details]    (footer)  │
└─────────────────────────────┘

Related Template & Helpers:useSlots()

❓ FAQ

Q What is the difference between slots and props?
A props pass data (any JS value); slots pass content (HTML/component fragments). Props suit configuration, slots suit content customization.
Q When should I use a scoped slot?
A When the parent needs to render custom markup based on the child's data — for example table column rendering, list item rendering, or the content layout of a card.
Q What is the difference between v-slot and the # shorthand?
A They are fully equivalent; #header is shorthand for v-slot:header. The shorthand is recommended because it is shorter.
Q Can the default slot be named?
A The default slot does not need a name — <slot /> is the default. If you really want to name it, use <slot name="default" /> and let the parent use <template #default> or pass content directly.
Q Can slots be passed through props?
A You cannot pass a slot as a prop directly, but you can pass a VNode / render function (an advanced technique). For normal cases just use <slot>.
Q How does the parent detect the child's slots?
A With $slots (in templates) or useSlots() (in JS). The child exposes them via <slot>, and the parent fills them with <template #name>.
Q Can named slots have default content?
A Yes. <slot name="header"><h3>Default Header</h3></slot> is shown when the parent passes no header.

📖 Summary


📝 Exercises

  1. Basic (difficulty ⭐) Implement a simple Button component:

    • BaseButton.vue: default slot + 3 variants (primary/success/danger)
    • props: variant (String)
    • Parent: test all 3 variants + custom slot content
  2. Intermediate (difficulty ⭐⭐) Implement a BaseLayout component:

    • Child: 3 named slots (header / default / footer)
    • Parent: build a Dashboard page with BaseLayout
    • Use $slots to detect whether each slot exists
    • Add fallback content
  3. Challenge (difficulty ⭐⭐⭐) Build a complete DataTable component system:

    1. DataTable.vue: receives columns + data and renders the table
    2. Scoped slots: #cell-{key} lets the parent customize a cell
    3. Default slot: shows the raw value when the parent provides nothing
    4. Parent examples: a user table + an order table, with 2 different cell renderers
    5. Performance: cache rows with v-memo (skip re-rendering when data is unchanged)
    6. TypeScript strong typing
Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏