Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Slots

Props pass data into a component, and slots pass markup. The component leaves a place in its template — the slot tag — and the parent decides what goes there. This is how wrappers are built: a card, a modal window, a list with a heading.

The default slot

Create src/components/BaseCard.vue:

<!-- src/components/BaseCard.vue -->
<template>
  <section class="card">
    <slot />
  </section>
</template>
 
<style scoped>
.card {
  border: 1px solid #ddd;
  border-radius: 8px;
  padding: 16px;
}
</style>

Whatever the parent writes between the component's opening and closing tags ends up in place of slot:

<!-- src/App.vue -->
<script setup>
import BaseCard from './components/BaseCard.vue'
</script>
 
<template>
  <BaseCard>
    <h2>Today</h2>
    <p>Buy bread, call Anna.</p>
  </BaseCard>
</template>

The page shows a frame with rounded corners, and inside it the heading and paragraph from App.vue.

Fallback content

Text inside the slot tag is shown when the parent passes nothing:

<template>
  <section class="card">
    <slot>Empty</slot>
  </section>
</template>

<BaseCard /> without content renders the frame with the word "Empty".

Named slots

When there are several places to fill, they get names. Create src/components/TodoList.vue:

<!-- src/components/TodoList.vue -->
<template>
  <div class="todo-list">
    <header><slot name="header" /></header>
    <ul><slot /></ul>
    <footer><slot name="footer" /></footer>
  </div>
</template>

The parent picks a slot with a template carrying the v-slot directive or its short form #:

<!-- src/App.vue, template fragment -->
<TodoList>
  <template #header>
    <h2>My tasks</h2>
  </template>
 
  <TodoItem v-for="todo in todos" :key="todo.id" :todo="todo" />
 
  <template #footer>
    Left: {{ remaining }}
  </template>
</TodoList>

The heading goes into header, the tasks into the default slot inside ul, the counter into footer.

Slots with data

Sometimes the component loops over the data itself, while the parent decides how a row looks. The component passes data to the slot as attributes:

<!-- src/components/TodoList.vue -->
<script setup>
defineProps({ todos: Array })
</script>
 
<template>
  <ul>
    <li v-for="todo in todos" :key="todo.id">
      <slot :todo="todo" />
    </li>
  </ul>
</template>
<!-- src/App.vue, template fragment -->
<TodoList :todos="todos" v-slot="{ todo }">
  <strong v-if="todo.done">{{ todo.text }}</strong>
  <span v-else>{{ todo.text }}</span>
</TodoList>

Finished tasks are shown in bold, the rest as plain text. The parent received todo from the component through v-slot.

Common mistake

Expecting slot content to see the child component's variables. Slot markup is written in the parent's template and has access only to the parent's data. If a component variable is needed inside the slot, the component must pass it explicitly as an attribute, like :todo="todo" above.

Thanks for reading!

Donate to the site — we work hard for you.