Tutorials Logic, IN info@tutorialslogic.com

Vue Slots Default, Named, Scoped Slots

Parent-owned Markup

Slots let a child component own behavior and layout while the parent supplies markup. The default slot handles the main body; named slots expose specific regions; scoped slots pass child-owned data to the parent’s slot template. This is a content contract, distinct from props that carry data values.

What are Slots?

Slots are Vue's content distribution mechanism - they let a parent component inject HTML content into a child component's template. Think of slots as placeholders that the parent fills in. This makes components highly reusable and flexible.

Type Description Syntax
Default slot Single unnamed slot <slot />
Named slots Multiple slots with names <slot name="header" />
Scoped slots Child passes data back to parent <slot :item="item" />
Fallback content Default content if no slot provided <slot>Default</slot>

Default, Named, and Scoped Slots

Default, Named, and Scoped Slots
<!-- components/BaseCard.vue -->
<template>
  <div class="card">
    <!-- Named slot: header -->
    <div v-if="$slots.header" class="card-header">
      <slot name="header" />
    </div>

    <!-- Default slot with fallback content -->
    <div class="card-body">
      <slot>
        <p class="tl-text-muted">No content provided.</p>
      </slot>
    </div>

    <!-- Named slot: footer -->
    <div v-if="$slots.footer" class="card-footer">
      <slot name="footer" />
    </div>
  </div>
</template>

<!-- components/Modal.vue -->
<template>
  <Teleport to="body">
    <div v-if="modelValue" class="modal-overlay" @click.self="$emit('update:modelValue', false)">
      <div class="modal">
        <div class="modal-header">
          <slot name="title"><h3>Modal</h3></slot>
          <button @click="$emit('update:modelValue', false)">×</button>
        </div>
        <div class="modal-body">
          <slot />
        </div>
        <div v-if="$slots.actions" class="modal-footer">
          <slot name="actions" />
        </div>
      </div>
    </div>
  </Teleport>
</template>

<script setup>
defineProps({ modelValue: Boolean })
defineEmits(['update:modelValue'])
</script>

What are Slots? - HTML Example

What are Slots? - HTML Example
<!-- components/DataTable.vue - scoped slots -->
<template>
  <table class="data-table">
    <thead>
      <tr>
        <th v-for="col in columns" :key="col.key">
          <!-- Scoped slot for column header -->
          <slot :name="`header-${col.key}`" :column="col">
            {{ col.label }}
          </slot>
        </th>
      </tr>
    </thead>
    <tbody>
      <tr v-for="(row, rowIndex) in data" :key="row.id || rowIndex">
        <td v-for="col in columns" :key="col.key">
          <!-- Scoped slot: passes row data to parent -->
          <slot
            :name="`cell-${col.key}`"
            :row="row"
            :value="row[col.key]"
            :index="rowIndex"
          >
            {{ row[col.key] }}
          </slot>
        </td>
      </tr>
    </tbody>
  </table>
</template>

<script setup>
defineProps({
  columns: { type: Array, required: true },
  data:    { type: Array, required: true },
})
</script>

What are Slots? - HTML Example 2

What are Slots? - HTML Example 2
<!-- Parent.vue -->
<template>
  <div>
    <!-- BaseCard with named slots -->
    <BaseCard>
      <template #header>
        <h2>User Profile</h2>
      </template>

      <!-- Default slot -->
      <p>Name: Alice Smith</p>
      <p>Email: alice@example.com</p>

      <template #footer>
        <button @click="edit">Edit</button>
        <button @click="delete_">Delete</button>
      </template>
    </BaseCard>

    <!-- card with no content - shows fallback -->
    <BaseCard />

    <!-- Modal with named slots -->
    <Modal v-model="showModal">
      <template #title><h3>Confirm Delete</h3></template>
      <p>Are you sure you want to delete this item?</p>
      <template #actions>
        <button @click="showModal = false">Cancel</button>
        <button @click="confirmDelete">Delete</button>
      </template>
    </Modal>

    <!-- DataTable with scoped slots -->
    <DataTable :columns="columns" :data="users">
      <!-- Custom cell rendering via scoped slot -->
      <template #cell-status="{ value }">
        <span :class="`badge badge-${value}`">{{ value }}</span>
      </template>

      <template #cell-actions="{ row }">
        <button @click="editUser(row)">Edit</button>
        <button @click="deleteUser(row.id)">Delete</button>
      </template>
    </DataTable>
  </div>
</template>

<script setup>
import { ref } from 'vue'
import BaseCard from './BaseCard.vue'
import Modal from './Modal.vue'
import DataTable from './DataTable.vue'

const showModal = ref(false)
const columns = [
  { key: 'name',    label: 'Name' },
  { key: 'email',   label: 'Email' },
  { key: 'status',  label: 'Status' },
  { key: 'actions', label: 'Actions' },
]
const users = ref([
  { id: 1, name: 'Alice', email: 'alice@example.com', status: 'active' },
  { id: 2, name: 'Bob',   email: 'bob@example.com',   status: 'inactive' },
])

function edit() { console.log('Edit') }
function delete_() { showModal.value = true }
function confirmDelete() { showModal.value = false; console.log('Deleted') }
function editUser(row) { console.log('Edit user:', row) }
function deleteUser(id) { users.value = users.value.filter(u => u.id !== id) }
</script>

Slot API Design

Expose only stable slot names and a small set of slot props. A component with many microscopic slots transfers its internal layout to every caller and becomes difficult to refactor. Provide useful fallback content when the component can operate without supplied markup.

Slot content is compiled in the parent scope, so it can read parent state but not arbitrary child internals. Scoped slot props are the explicit bridge. Keep accessibility responsibilities clear: if the child owns a dialog or menu structure, it should preserve required roles and relationships regardless of custom slot content.

Slot Contracts, Fallbacks, and Scoped Data

A slot lets the parent provide template content while the child controls where that content appears. Default content inside slot is a fallback used only when the parent supplies nothing. Named slots create separate regions, and scoped slots expose child-owned data to the parent template without transferring ownership of that state.

Treat slot names and slot props as a public component API. Use stable names, document the shape of exposed values, and avoid exposing the child's entire internal state. The parent can use slot props only inside the template bound to that slot.

Expose a narrow row slot

Expose a narrow row slot
<!-- DataList.vue -->
<ul>
  <li v-for="(item, index) in items" :key="item.id">
    <slot name="row" :item="item" :index="index">
      {{ item.label }}
    </slot>
  </li>
</ul>

<!-- Parent.vue -->
<DataList :items="users">
  <template #row="{ item, index }">
    {{ index + 1 }}. {{ item.name }}
  </template>
</DataList>

The child owns iteration and exposes only the item and index needed to customize one row.

Dynamic Names and Type-Safe Slots

Dynamic slot names use v-slot:[expression] when the region is selected at runtime. Use them for real layout variation, not to hide an unstable component API. Forwarding slots through a wrapper requires deliberately binding each slot and its props; otherwise wrappers can silently drop content or scope data.

In TypeScript single-file components, defineSlots can describe slot names, accepted props, and expected content for editor and build-time checking. Slot typing does not validate runtime data from a server, so keep normal input validation at the component boundary.

  • Use #header as shorthand for v-slot:header.
  • Place v-slot on template when several elements belong to the same slot.
  • Do not combine multiple templates for the same named slot at one component use site.
  • Provide a fallback when missing content would otherwise leave an unusable control or unlabeled region.
Before you move on

Vue Slots Default, Named, Scoped Slots Mastery Check

3 checks
  • Slots are Vue's content distribution mechanism - they let a parent component inject HTML content into a child component's template.
  • Think of slots as placeholders that the parent fills in.
  • This makes components highly reusable and flexible.
Browse Free Tutorials

Explore 500+ free tutorials across 20+ languages and frameworks.