| |

Vue.js 36 🟢 Scoped Slots for Passing Data from Child to Slot Content

A regular slot passes content in one direction. The parent provides the markup, the child places it, and the child has no say in what the content contains. But sometimes the child needs to share data with the content it renders. A list component knows the item and the index, and it wants the parent to decide how each item looks. A data table knows the row and the column, and it wants the parent to control the cell rendering. A form field knows the validation state, and it wants the parent to display the error. The regular slot cannot do this because the slot content is compiled in the parent’s scope, and the child’s data is not available there. The scoped slot solves the problem by letting the child pass the data up to the slot content, so the parent’s template can use it.

The mechanism is the slot props. The child attaches the data to the <slot> element as attributes, and the parent receives them as the slot props. The syntax in the child is <slot :item="item" :index="index" />, and the syntax in the parent is <template #default="{ item, index }">...</template> or <template #item="{ item, index }">...</template>. The parent’s slot content is still compiled in the parent’s scope, but the slot props are the exception: they are the child’s data injected into the parent’s template. This is what makes the scoped slot the reusable rendering primitive. The component provides the data and the structure, and the parent provides the presentation.

The scoped slot is the pattern behind the renderless components, the headless UI libraries, and the advanced composition. A component like <Fetch :url="..."> can pass the data, the loading, and the error to the slot content, and the parent decides how to render each state. A component like <List :items="..."> can pass the item and the index, and the parent decides the item markup. The result is the separation of the behavior and the presentation. The component owns the logic, and the parent owns the look. The scoped slot is the API that connects them.

This chapter covers three areas. First, why the scoped slots exist — the presentation problem and the separation of the behavior and the look. Second, the syntax — the <slot :prop="value" /> in the child, the #default="{ prop }" in the parent, and the named scoped slots. Third, the patterns and the pitfalls — the renderless components, the list rendering, the multiple scoped slots, the destructuring, and the performance considerations. The chapter ends with a complete example session, a quick reference, best practices, common pitfalls, real-world examples, and diagrams showing the scoped slot flow.

Key point: A scoped slot lets the child pass data to the parent’s slot content. The child attaches the data to the <slot> as attributes, and the parent receives it as the slot props with the #default="{ prop }" or the #name="{ prop }" syntax. The slot content is compiled in the parent’s scope, but the slot props are the child’s data. The scoped slot is the pattern behind the renderless components, the headless UI, and the reusable list rendering.


Why the scoped slots exist

The presentation problem. The component knows the behavior and the structure, but it cannot know the presentation. The list component knows the items, the order, and the selection state, but it cannot know how each item should look. The table component knows the rows and the columns, but it cannot know the cell format. The form field knows the validation state, but it cannot know the error message. The component should not hardcode the presentation, because the presentation varies by the application. The scoped slot is the mechanism that lets the parent provide the presentation while the component provides the data.

The data-flow problem. The regular slot content is compiled in the parent’s scope. It has access to the parent’s data, but not to the child’s data. The child’s item is the loop variable inside the component, and the parent’s template cannot see it. The scoped slot changes this: the child passes the data as the slot props, and the parent’s template receives it. The parent’s slot content is still the parent’s scope, but the child’s data is the injected. The two scopes connect at the slot.

The reusability problem. A component that hardcodes the item rendering is the reusable only for the one look. A component that exposes the item via the scoped slot is the reusable for any look. The same <List> component renders the text list in the one place, the card grid in the another, and the table in the third. The behavior is the shared, and the presentation is the variable. The scoped slot is the reuse at the presentation level.

The renderless-component problem. A renderless component provides the behavior and no markup. The <Fetch> component manages the loading state and the error handling, and the parent decides the markup for each state. The <Mouse> component tracks the mouse position and passes the x and the y, and the parent decides what to do with them. The <Toggle> component manages the boolean and the toggle function, and the parent decides the button. The scoped slot is the API that makes the renderless component possible.

The composition problem. The scoped slot composes with the named slots. The <DataTable> component exposes the #header and the #row slots, and the parent controls both the header and the row rendering. The <Select> component exposes the #selected and the #option slots. The multiple scoped slots let the parent control the multiple parts of the component.

The trade-off. The scoped slot adds the indirection: the parent must know the prop names, and the child must document them. The slot content is the function-like, and the wrong prop name is the silent undefined. The trade-off is between the reusability of the component and the explicitness of the contract.


a. The scoped slot syntax

The child attaches the data to the <slot> as the attributes:

<!-- List.vue -->
<script setup>
defineProps({
  items: Array,
});
</script>

<template>
  <ul>
    <li v-for="(item, index) in items" :key="item.id">
      <slot :item="item" :index="index" />
    </li>
  </ul>
</template>

The :item and the :index are the slot props. The parent receives them:

<template>
  <List :items="items">
    <template #default="{ item, index }">
      <span>{{ index }}: {{ item.name }}</span>
    </template>
  </List>
</template>

The #default="{ item, index }" destructures the slot props. The item and the index are the child’s data, and the parent’s template uses them. The parent’s slot content is compiled in the parent’s scope, and the slot props are the child’s data .

The v-slot long form. The #default is the shorthand for the v-slot:default:

<List :items="items">
  <template v-slot:default="{ item, index }">
    <span>{{ index }}: {{ item.name }}</span>
  </template>
</List>

The two are equivalent, and the # is the recommended.

The shorthand on the component. The v-slot can be on the component itself for the default slot:

<List :items="items" v-slot="{ item, index }">
  <span>{{ index }}: {{ item.name }}</span>
</List>

The v-slot on the component targets the default slot. The named slots still need the <template>.

The default value. The slot props can have the default values:

<template #default="{ item, index = 0 }">
  <span>{{ index }}: {{ item.name }}</span>
</template>

The index = 0 provides the default when the child does not pass the index.

The no destructuring. The parent can receive the whole props object:

<template #default="slotProps">
  <span>{{ slotProps.index }}: {{ slotProps.item.name }}</span>
</template>

The slotProps is the whole object, and the properties are the accessed with the dot notation.


b. The named scoped slots

The named scoped slots combine the name and the props:

<!-- DataTable.vue -->
<script setup>
defineProps({
  rows: Array,
  columns: Array,
});
</script>

<template>
  <table>
    <thead>
      <tr>
        <th v-for="col in columns" :key="col.key">
          <slot name="header" :column="col" :index="col.index" />
        </th>
      </tr>
    </thead>
    <tbody>
      <tr v-for="row in rows" :key="row.id">
        <td v-for="col in columns" :key="col.key">
          <slot name="cell" :row="row" :column="col" :value="row[col.key]" />
        </td>
      </tr>
    </tbody>
  </table>
</template>

The child exposes the two named scoped slots: the header with the column and the index, and the cell with the row, the column, and the value. The parent uses them:

<DataTable :rows="rows" :columns="columns">
  <template #header="{ column }">
    <strong>{{ column.label }}</strong>
  </template>

  <template #cell="{ row, column, value }">
    <span v-if="column.key === 'status'" :class="`status-${value}`">
      {{ value }}
    </span>
    <span v-else>{{ value }}</span>
  </template>
</DataTable>

The #header controls the header rendering, and the #cell controls the cell rendering. The parent has the full control over the presentation.

The multiple scoped slots. The component can expose the multiple scoped slots, and the parent can fill them independently.

The optional slots. The $slots check determines whether the parent filled the slot:

<template>
  <th>
    <slot name="header" :column="col">
      {{ col.label }}
    </slot>
  </th>
</template>

The fallback content is the col.label, and the parent can override it with the #header.


c. The patterns and the pitfalls

The renderless component. The component provides the behavior and no markup:

<!-- Fetch.vue -->
<script setup>
import { ref, watchEffect } from 'vue';

const props = defineProps({
  url: String,
});

const data = ref(null);
const loading = ref(true);
const error = ref(null);

watchEffect(async () => {
  loading.value = true;
  error.value = null;
  try {
    const response = await fetch(props.url);
    data.value = await response.json();
  } catch (e) {
    error.value = e;
  } finally {
    loading.value = false;
  }
});
</script>

<template>
  <slot :data="data" :loading="loading" :error="error" />
</template>

The parent decides the markup for the loading, the error, and the data:

<Fetch url="/api/users" v-slot="{ data, loading, error }">
  <div v-if="loading">Loading...</div>
  <div v-else-if="error">Error: {{ error.message }}</div>
  <ul v-else>
    <li v-for="user in data" :key="user.id">{{ user.name }}</li>
  </ul>
</Fetch>

The Fetch component owns the fetching logic, and the parent owns the presentation.

The list rendering. The list component exposes the item and the index:

<List :items="items" v-slot="{ item, index }">
  <Card>
    <h3>{{ index + 1 }}. {{ item.title }}</h3>
    <p>{{ item.description }}</p>
  </Card>
</List>

The parent decides the item markup, and the list component provides the iteration.

The multiple scoped slots. The component exposes the multiple slots, and the parent fills the ones it needs:

<DataTable :rows="rows" :columns="columns">
  <template #header="{ column }">
    <SortableHeader :column="column" />
  </template>

  <template #cell="{ row, column, value }">
    <EditableCell :row="row" :column="column" :value="value" />
  </template>
</DataTable>

The destructuring pitfall. The destructuring in the slot props is the reactive, but the reassignment is not:

<!-- ❌ The wrong -->
<template #default="{ item }">
  <button @click="item = null">Clear</button>
</template>

<!-- ✅ The correct -->
<template #default="{ item, onClear }">
  <button @click="onClear">Clear</button>
</template>

The slot props are the readonly, and the child must expose the callback.

The prop-naming pitfall. The slot prop names are the contract. The wrong name is the silent undefined:

<!-- The child -->
<slot :item="item" :index="index" />

<!-- ❌ The wrong name -->
<template #default="{ itm }">{{ itm.name }}</template>

The itm is the undefined, and the error is the runtime.

The v-slot on the component. The v-slot on the component targets the default slot. The named slots need the <template>:

<!-- ✅ The default slot -->
<Fetch :url="url" v-slot="{ data }">...</Fetch>

<!-- ✅ The named slot -->
<Fetch :url="url">
  <template #error="{ error }">...</template>
</Fetch>

The performance. The scoped slot content is the re-rendered when the slot props change. The parent should avoid the expensive computation inside the slot content, and the child should pass the stable references.


Complete Example Session

<!-- ============================================
     PART 1: THE BASIC SCOPED SLOT
     ============================================ -->

<!-- List.vue -->
<script setup>
defineProps({ items: Array });
</script>

<template>
  <ul>
    <li v-for="(item, index) in items" :key="item.id">
      <slot :item="item" :index="index" />
    </li>
  </ul>
</template>

<!-- The parent -->
<template>
  <List :items="items">
    <template #default="{ item, index }">
      <span>{{ index }}: {{ item.name }}</span>
    </template>
  </List>
</template>


<!-- ============================================
     PART 2: THE v-slot LONG FORM
     ============================================ -->

<List :items="items">
  <template v-slot:default="{ item, index }">
    <span>{{ index }}: {{ item.name }}</span>
  </template>
</List>


<!-- ============================================
     PART 3: THE SHORTHAND ON THE COMPONENT
     ============================================ -->

<List :items="items" v-slot="{ item, index }">
  <span>{{ index }}: {{ item.name }}</span>
</List>


<!-- ============================================
     PART 4: THE NAMED SCOPED SLOTS
     ============================================ -->

<!-- DataTable.vue -->
<script setup>
defineProps({ rows: Array, columns: Array });
</script>

<template>
  <table>
    <thead>
      <tr>
        <th v-for="col in columns" :key="col.key">
          <slot name="header" :column="col" />
        </th>
      </tr>
    </thead>
    <tbody>
      <tr v-for="row in rows" :key="row.id">
        <td v-for="col in columns" :key="col.key">
          <slot name="cell" :row="row" :column="col" :value="row[col.key]" />
        </td>
      </tr>
    </tbody>
  </table>
</template>

<!-- The parent -->
<DataTable :rows="rows" :columns="columns">
  <template #header="{ column }">
    <strong>{{ column.label }}</strong>
  </template>

  <template #cell="{ row, column, value }">
    <span>{{ value }}</span>
  </template>
</DataTable>


<!-- ============================================
     PART 5: THE RENDERLESS COMPONENT
     ============================================ -->

<!-- Fetch.vue -->
<script setup>
import { ref, watchEffect } from 'vue';

const props = defineProps({ url: String });

const data = ref(null);
const loading = ref(true);
const error = ref(null);

watchEffect(async () => {
  loading.value = true;
  error.value = null;
  try {
    const response = await fetch(props.url);
    data.value = await response.json();
  } catch (e) {
    error.value = e;
  } finally {
    loading.value = false;
  }
});
</script>

<template>
  <slot :data="data" :loading="loading" :error="error" />
</template>

<!-- The parent -->
<Fetch url="/api/users" v-slot="{ data, loading, error }">
  <div v-if="loading">Loading...</div>
  <div v-else-if="error">Error: {{ error.message }}</div>
  <ul v-else>
    <li v-for="user in data" :key="user.id">{{ user.name }}</li>
  </ul>
</Fetch>


<!-- ============================================
     PART 6: THE MULTIPLE SCOPED SLOTS
     ============================================ -->

<!-- Toggle.vue -->
<script setup>
import { ref } from 'vue';

const isOn = ref(false);
const toggle = () => (isOn.value = !isOn.value);
</script>

<template>
  <slot name="on" :toggle="toggle" v-if="isOn" />
  <slot name="off" :toggle="toggle" v-else />
</template>

<!-- The parent -->
<Toggle>
  <template #on="{ toggle }">
    <button @click="toggle">The light is ON — click to turn off</button>
  </template>
  <template #off="{ toggle }">
    <button @click="toggle">The light is OFF — click to turn on</button>
  </template>
</Toggle>


<!-- ============================================
     PART 7: THE DESTRUCTURING PITFALL
     ============================================ -->

<!-- ❌ The wrong -->
<template #default="{ item }">
  <button @click="item = null">Clear</button>
</template>

<!-- ✅ The correct -->
<template #default="{ item, onClear }">
  <button @click="onClear">Clear</button>
</template>


<!-- ============================================
     PART 8: THE PROP-NAMING PITFALL
     ============================================ -->

<!-- The child -->
<slot :item="item" :index="index" />

<!-- ❌ The wrong name -->
<template #default="{ itm }">{{ itm.name }}</template>

<!-- ✅ The correct -->
<template #default="{ item }">{{ item.name }}</template>


<!-- ============================================
     PART 9: THE FALLBACK CONTENT
     ============================================ -->

<!-- DataTable.vue -->
<template>
  <th>
    <slot name="header" :column="col">
      {{ col.label }}
    </slot>
  </th>
</template>

<!-- The fallback is the col.label, and the parent can override it. -->


<!-- ============================================
     PART 10: THE COMPLETE EXAMPLE
     ============================================ -->

<!-- DataList.vue -->
<script setup>
defineProps({
  items: { type: Array, required: true },
  loading: { type: Boolean, default: false },
  error: { type: String, default: null },
});
</script>

<template>
  <div>
    <div v-if="loading" class="loading">
      <slot name="loading">Loading...</slot>
    </div>

    <div v-else-if="error" class="error">
      <slot name="error" :error="error">{{ error }}</slot>
    </div>

    <ul v-else class="list">
      <li v-for="(item, index) in items" :key="item.id">
        <slot name="item" :item="item" :index="index" :total="items.length">
          {{ item.name }}
        </slot>
      </li>
    </ul>
  </div>
</template>

<!-- The parent -->
<template>
  <DataList
    :items="users"
    :loading="loading"
    :error="error"
  >
    <template #loading>
      <Spinner />
    </template>

    <template #error="{ error }">
      <ErrorBanner :message="error" />
    </template>

    <template #item="{ item, index, total }">
      <Card>
        <h3>{{ index + 1 }} / {{ total }}: {{ item.name }}</h3>
        <p>{{ item.email }}</p>
      </Card>
    </template>
  </DataList>
</template>

The ten parts show the basic scoped slot, the v-slot long form, the shorthand on the component, the named scoped slots, the renderless component, the multiple scoped slots, the destructuring pitfall, the prop-naming pitfall, the fallback content, and the complete example.


Quick Reference

The Syntax

In the childIn the parent
<slot :item="item" /><template #default="{ item }">
<slot name="header" :col="col" /><template #header="{ col }">
<slot :item="item" :index="index" /><template #default="{ item, index }">
<slot :item="item" /><template #default="props">{{ props.item }}

The Named Scoped Slots

The childThe parent
<slot name="header" :col="col" /><template #header="{ col }">
<slot name="cell" :row="row" /><template #cell="{ row }">
<slot name="item" :item="item" /><template #item="{ item }">

The Shorthand Forms

The formThe target
#default="{ item }"The default slot
#header="{ col }"The named slot
v-slot="{ item }" on the componentThe default slot
v-slot:header="{ col }"The named slot

The Renderless Component

The childThe parent
<slot :data="data" :loading="loading" />v-slot="{ data, loading }"
<slot :x="x" :y="y" />v-slot="{ x, y }"
<slot :isOn="isOn" :toggle="toggle" />v-slot="{ isOn, toggle }"

The Fallback Content

The syntaxThe behavior
<slot :item="item">{{ item.name }}</slot>The fallback
<slot name="header" :col="col">{{ col.label }}</slot>The named fallback

Best Practices

✅ Do This:

<!-- Use the scoped slot for the reusable rendering -->
<List :items="items" v-slot="{ item, index }">...</List>                 <!-- ✅ -->
<!-- Use the named scoped slots for the multi-part -->
<DataTable><template #cell="{ row }">...</template></DataTable>          <!-- ✅ -->
<!-- Use the renderless component for the behavior -->
<Fetch url="/api" v-slot="{ data, loading, error }">...</Fetch>          <!-- ✅ -->
<!-- Use the fallback content -->
<slot :item="item">{{ item.name }}</slot>                                <!-- ✅ -->
<!-- Use the destructuring with the defaults -->
<template #default="{ item, index = 0 }">...</template>                  <!-- ✅ -->
<!-- Expose the callbacks for the mutations -->
<slot :item="item" :onClear="() => remove(item.id)" />                   <!-- ✅ -->

❌ Don’t Do This:

<!-- Don't reassign the slot props -->
<template #default="{ item }">
  <button @click="item = null">Clear</button>                            // ❌
</template>
<!-- Don't use the wrong prop name -->
<template #default="{ itm }">{{ itm.name }}</template>                   // ❌
<!-- Don't forget the named slot template -->
<Fetch :url="url" v-slot="{ data }">
  <template #error="{ error }">...</template>                            // ✅
</Fetch>
<!-- Don't hide the behavior in the parent -->
<Fetch url="/api" v-slot="{ data }">
  <MyOwnFetchLogic :data="data" />                                       // ❌
</Fetch>
<!-- Don't compute the expensive values in the slot -->
<template #default="{ item }">
  {{ expensiveComputation(item) }}                                       // ❌
</template>

Common Pitfalls

The pitfallThe whyThe fix
The slot prop is undefinedThe wrong nameCheck the name
The slot props are not reactiveThe wrong bindingCheck the :prop
The named slot not filledThe wrong #nameCheck the name
The default slot ignoredThe missing #defaultAdd the template
The destructuring reassignmentThe readonlyUse the callback
The fallback not renderedThe parent filledRemove the parent content
The v-slot on the component with the namedThe wrong targetUse the <template>

Real-World Examples

1. The Basic Scoped Slot

<List :items="items" v-slot="{ item }">{{ item.name }}</List>

2. The Named Scoped Slot

<template #header="{ column }">{{ column.label }}</template>

3. The Renderless Fetch

<Fetch url="/api" v-slot="{ data, loading, error }">...</Fetch>

4. The Renderless Toggle

<Toggle v-slot="{ isOn, toggle }">...</Toggle>

5. The Table

<DataTable><template #cell="{ row, column }">...</template></DataTable>

6. The Fallback

<slot :item="item">{{ item.name }}</slot>

7. The Defaults

<template #default="{ item, index = 0 }">...</template>

8. The Callback

<slot :item="item" :onClear="onClear" />

9. The Multi-Slot

<template #on="{ toggle }">...</template>
<template #off="{ toggle }">...</template>

10. The Complete

<DataList :items="users" v-slot:item="{ item, index, total }">...</DataList>

Visual

The Scoped Slot Flow

┌──────────────────────────────────────────────────────────────┐
│  THE SCOPED SLOT FLOW                                        │
│                                                              │
│  The child:                                                  │
│  ┌──────────────────────────────────────────────────────┐    │
│  │  <li v-for="(item, index) in items">                 │    │
│  │    <slot :item="item" :index="index" />              │    │
│  │  </li>                                               │    │
│  │                                                       │    │
│  │  The child passes the item and the index.            │    │
│  └──────────────────────────────────────────────────────┘    │
│    │                                                         │
│    │  the slot props                                        │
│    ▼                                                         │
│  The parent:                                                 │
│  ┌──────────────────────────────────────────────────────┐    │
│  │  <List :items="items">                               │    │
│  │    <template #default="{ item, index }">             │    │
│  │      <span>{{ index }}: {{ item.name }}</span>       │    │
│  │    </template>                                       │    │
│  │  </List>                                             │    │
│  │                                                       │    │
│  │  The parent uses the item and the index.             │    │
│  └──────────────────────────────────────────────────────┘    │
│                                                              │
└──────────────────────────────────────────────────────────────┘

The Renderless Component

┌──────────────────────────────────────────────────────────────┐
│  THE RENDERLESS COMPONENT                                    │
│                                                              │
│  The Fetch component:                                        │
│  ┌──────────────────────────────────────────────────────┐    │
│  │  <script setup>                                      │    │
│  │  const data = ref(null);                             │    │
│  │  const loading = ref(true);                          │    │
│  │  const error = ref(null);                            │    │
│  │  // the fetching logic                               │    │
│  │  </script>                                           │    │
│  │                                                       │    │
│  │  <template>                                          │    │
│  │    <slot :data="data" :loading="loading"             │    │
│  │          :error="error" />                           │    │
│  │  </template>                                         │    │
│  │                                                       │    │
│  │  The component provides the data, the loading,       │    │
│  │  and the error. No markup.                           │    │
│  └──────────────────────────────────────────────────────┘    │
│    │                                                         │
│    │  the slot props                                        │
│    ▼                                                         │
│  The parent:                                                 │
│  ┌──────────────────────────────────────────────────────┐    │
│  │  <Fetch url="/api" v-slot="{ data, loading, error }">│    │
│  │    <div v-if="loading">Loading...</div>              │    │
│  │    <div v-else-if="error">{{ error.message }}</div>  │    │
│  │    <ul v-else>...</ul>                               │    │
│  │  </Fetch>                                            │    │
│  │                                                       │    │
│  │  The parent provides the markup.                     │    │
│  └──────────────────────────────────────────────────────┘    │
│                                                              │
└──────────────────────────────────────────────────────────────┘

The Named Scoped Slots

┌──────────────────────────────────────────────────────────────┐
│  THE NAMED SCOPED SLOTS                                      │
│                                                              │
│  The DataTable component:                                    │
│  ┌──────────────────────────────────────────────────────┐    │
│  │  <slot name="header" :column="col" />                │    │
│  │  <slot name="cell" :row="row" :value="value" />      │    │
│  └──────────────────────────────────────────────────────┘    │
│    │                                                         │
│    ▼                                                         │
│  The parent:                                                 │
│  ┌──────────────────────────────────────────────────────┐    │
│  │  <DataTable>                                         │    │
│  │    <template #header="{ column }">                   │    │
│  │      <strong>{{ column.label }}</strong>             │    │
│  │    </template>                                       │    │
│  │                                                       │    │
│  │    <template #cell="{ row, column, value }">         │    │
│  │      <span>{{ value }}</span>                        │    │
│  │    </template>                                       │    │
│  │  </DataTable>                                        │    │
│  └──────────────────────────────────────────────────────┘    │
│                                                              │
└──────────────────────────────────────────────────────────────┘

The Destructuring Pitfall

┌──────────────────────────────────────────────────────────────┐
│  THE DESTRUCTURING PITFALL                                   │
│                                                              │
│  ❌ THE WRONG:                                                │
│  ┌──────────────────────────────────────────────────────┐    │
│  │  <template #default="{ item }">                      │    │
│  │    <button @click="item = null">Clear</button>       │    │
│  │  </template>                                         │    │
│  │                                                       │    │
│  │  The item is the readonly slot prop.                 │    │
│  │  The reassignment does not propagate to the child.   │    │
│  └──────────────────────────────────────────────────────┘    │
│                                                              │
│  ✅ THE CORRECT:                                              │
│  ┌──────────────────────────────────────────────────────┐    │
│  │  The child:                                          │    │
│  │    <slot :item="item" :onClear="() => remove(item)" />│   │
│  │                                                       │    │
│  │  The parent:                                         │    │
│  │    <template #default="{ item, onClear }">           │    │
│  │      <button @click="onClear">Clear</button>         │    │
│  │    </template>                                       │    │
│  │                                                       │    │
│  │  The child exposes the callback.                     │    │
│  └──────────────────────────────────────────────────────┘    │
│                                                              │
└──────────────────────────────────────────────────────────────┘

Summary

ItemValue
The scoped slotThe child passes the data to the parent’s slot content
The slot propsThe <slot :prop="value" /> attributes
The default scoped slotThe #default="{ prop }"
The named scoped slotThe #name="{ prop }"
The renderless componentThe behavior without the markup
The destructuringThe readonly
The callbackThe child exposes the mutation
The fallbackThe content between the <slot> tags

Key takeaways:

  • A scoped slot lets the child pass the data to the parent’s slot content. The child attaches the data to the <slot> as the attributes, and the parent receives it as the slot props with the #default="{ prop }" or the #name="{ prop }" syntax.
  • The slot content is compiled in the parent’s scope, but the slot props are the child’s data. The two scopes connect at the slot. The child provides the data and the structure, and the parent provides the presentation.
  • The renderless component is the pattern. The component provides the behavior and no markup, and the parent decides the markup for each state. The <Fetch>, the <Toggle>, and the <Mouse> are the examples.
  • The named scoped slots combine the name and the props. The #header="{ column }" and the #cell="{ row, value }" control the multiple parts of the component.
  • The destructuring is the readonly. The parent cannot reassign the slot prop. The child must expose the callback for the mutation.
  • The prop names are the contract. The wrong name is the silent undefined. The child must document the prop names, and the parent must use them correctly.
  • The fallback content is the default. The content between the <slot> tags is rendered when the parent does not provide the slot content.
  • The result is the reusable rendering. The component owns the behavior, and the parent owns the look. The scoped slot is the API that connects them.

Remember: A scoped slot lets the child pass the data to the parent’s slot content. The child attaches the data to the <slot>, and the parent receives it as the slot props. The renderless component is the pattern, and the named scoped slots control the multiple parts. The result is the reusable rendering, which is the foundation of the headless UI and the advanced composition.



Stop using slow, ad-bloated tool sites! 🤮

🔎 Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)

⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free

🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!