Scoped Slots

Child Hands Data to the Parent's Markup

Scoped Slots

A child can expose data to slot content through slot bindings. This is the trick behind renderless components and headless UI libraries.

4 min read Level 3/5 #vue#slots
What you'll learn
  • Bind data on a slot
  • Destructure in the parent with v-slot
  • Build a renderless component

A scoped slot lets the child component pass values back to the parent’s slot content. The parent receives those values as props on the slot.

Binding Data on a Slot

<!-- UserList.vue -->
<script setup lang="ts">
import { ref } from 'vue'
const users = ref([{ id: 1, name: 'Ada' }, { id: 2, name: 'Linus' }])
const loading = ref(false)
</script>

<template>
  <div v-for="u in users" :key="u.id">
    <slot :user="u" :loading="loading" />
  </div>
</template>

Consuming in the Parent

Use v-slot (or the # shorthand) to capture the slot props. Destructuring keeps it clean.

<UserList v-slot="{ user, loading }">
  <p v-if="loading">Loading…</p>
  <p v-else>{{ user.name }}</p>
</UserList>

For named scoped slots, combine the name with the destructure:

<DataTable :rows="rows">
  <template #cell="{ row, column }">
    <strong>{{ row[column.key] }}</strong>
  </template>
</DataTable>

Renderless Components

A renderless component does logic only and exposes everything through scoped slots. The parent decides the markup.

<!-- Toggle.vue -->
<script setup lang="ts">
import { ref } from 'vue'
const on = ref(false)
const toggle = () => (on.value = !on.value)
</script>

<template>
  <slot :on="on" :toggle="toggle" />
</template>
<Toggle v-slot="{ on, toggle }">
  <button @click="toggle">{{ on ? 'Hide' : 'Show' }}</button>
  <p v-if="on">Surprise!</p>
</Toggle>
Dynamic Components →