Vue.js course Β· Module 8: Slots & Dynamic Components
Slots - Basics
In this lesson6
Welcome to the NOVA LAB Module Storage! Here every cargo container has standardized docking ports - you can plug in different content while keeping the same outer structure. Before you meet the mechanism, feel the problem: you are building a CargoBay component with a frame, a header and a border. One bay holds oxygen tanks, another a list of filters, a third a whole status panel. Props are great at carrying a title or a number, but they can't carry a piece of template with formatting and other components. Without a new tool you would have to copy CargoBay for every kind of cargo. In Vue that tool is slots - they allow a parent to inject arbitrary content into a child component.
What you will learn
In the Module Storage you will master the whole component docking system:
- inserting content into a component through the default slot and giving it fallback content,
- splitting a component into several ports with named slots,
- passing data from the child to the slot content with scoped slots,
- switching station panels with
<component :is>and keeping their state in<KeepAlive>, - displaying alarms outside the component hierarchy with
<Teleport>, - loading heavy modules on demand with async components and
<Suspense>.
What is a Slot?
A slot is a place in a child component's template that the parent can fill with its own content. It's like a docking port in a cargo bay - the component defines where the opening is, and the parent decides what to put in it. The Vue documentation calls the <slot> element a slot outlet, and what the parent passes is the slot content. Below you see the child first, CargoBay.vue, and under it the parent, App.vue, which puts two paragraphs between the component's tags:
1<!-- CargoBay.vue (child) -->
2<template>
3 <div class="cargo-bay">
4 <h2>Cargo Bay Alpha</h2>
5 <div class="content">
6 <slot></slot>
7 </div>
8 </div>
9</template>
10
11<!-- App.vue (parent) -->
12<template>
13 <CargoBay>
14 <p>Oxygen Tank OX-2087</p>
15 <p>Weight: 250 kg</p>
16 </CargoBay>
17</template>The parent places content between the component's tags, and that content goes where <slot></slot> is. The frame, the "Cargo Bay Alpha" header and the CSS classes did not change by a single pixel - CargoBay doesn't know what it carries, and it doesn't need to.
How Vue renders a slot
It's worth understanding what happens under the hood, because it explains how slots behave in the next lessons. The whole process takes four steps:
- The child component defines
<slot>in its template - that is the docking port. - The parent component passes content between the child's tags.
- When Vue renders the child, it replaces
<slot>with the passed content. Technically the compiler turns the parent's content into a function, and the child calls it exactly where the outlet is. - The content appears in the DOM at the slot's position, in our case inside
div.content.
The <slot> tag itself does not stay in the final HTML - in the browser you only see the parent's paragraphs inside the child's frame.
Fallback Content
What if the parent leaves the port empty? Content written between <slot> and </slot> in the child is fallback content, and it is displayed only when the parent passes nothing:
1<!-- CargoBay.vue -->
2<template>
3 <div class="cargo-bay">
4 <slot>
5 <p>Cargo bay empty - awaiting equipment</p>
6 </slot>
7 </div>
8</template>
9
10<!-- Usage without content - displays fallback -->
11<CargoBay />
12
13<!-- Usage with content - replaces fallback -->
14<CargoBay>
15 <p>Water Recycler Unit</p>
16</CargoBay>The first usage shows the empty cargo bay message, and the second replaces it entirely with the water recycler description. Fallback content is never merged with the parent's content: the screen shows either one or the other.
Slot Scope
Slot content is compiled in the parent's scope, not the child's. This means that inside the slot content you have access to the parent's data, as in this example with the moduleName variable declared in App.vue:
1<!-- App.vue -->
2<script setup>
3import { ref } from 'vue'
4const moduleName = ref('Oxygen Generator')
5</script>
6
7<template>
8 <CargoBay>
9 <!-- moduleName comes from the PARENT -->
10 <p>{{ moduleName }}</p>
11 </CargoBay>
12</template>Vue looks for moduleName where the content was written, that is in App.vue. It works the other way round too: slot content does not see the child's data, so a variable declared in CargoBay would not be available in it. You will learn how to show the child's data in a slot anyway in the lesson on scoped slots.
Multiple Elements in a Slot
You can put as many elements into one port as you like: a heading, a list, even another component, such as a StatusBadge that shows the cargo status badge:
1<template>
2 <CargoBay>
3 <h3>Critical Equipment</h3>
4 <ul>
5 <li>Oxygen Tank</li>
6 <li>Water Filter</li>
7 </ul>
8 <StatusBadge status="loaded" />
9 </CargoBay>
10</template>Everything between the tags goes into one default slot, in the same order you wrote it. When you want to split content across several places, for example a header in one spot and buttons in another, you will need named slots from the next lesson.
My advice: when a component has to display someone else's content - formatted text, buttons, other components - reach for a slot, not a string prop. Keep props for simple values such as a title or a number. And always give a slot sensible fallback content, because an empty cargo bay without a message looks like a malfunction. In the editor below, CargoBay appears three times: with a tank description, with a list of modules and completely empty, so you can see the fallback in action.
Remember: a slot is a docking port - the component provides the frame, and the parent decides which cargo to plug into it.
Code for this lesson: App.vue
1<script setup>
2import CargoBay from './CargoBay.vue'
3</script>
4
5<template>
6 <div class="nova-lab">
7 <h1>NOVA LAB - Slots Demo</h1>
8
9 <!-- Slot with content -->
10 <CargoBay>
11 <h3>Oxygen Tank OX-2087</h3>
12 <p>Weight: 250 kg | Status: Loaded</p>
13 </CargoBay>
14
15 <!-- Slot with component and HTML -->
16 <CargoBay>
17 <ul>
18 <li>Water Filter WF-01</li>
19 <li>Solar Cell SC-44</li>
20 <li>Thermal Shield TS-12</li>
21 </ul>
22 </CargoBay>
23
24 <!-- Empty slot - will show fallback -->
25 <CargoBay />
26 </div>
27</template>
28
29<style scoped>
30.nova-lab {
31 background: #0a0e27;
32 color: #00ff88;
33 min-height: 100vh;
34 padding: 20px;
35 font-family: monospace;
36}
37h1 { color: #00ff88; margin-bottom: 20px; }
38</style>Spotted a mistake in this lesson?
Check yourself
Answer the questions from this lesson. Pick an answer to see right away whether it is correct.
1. What is a slot in Vue.js?
2. What happens when a parent doesn't pass content to a slot that has fallback content?
These are 2 of 3 questions for this lesson. Solve the rest in the game.
Hands-on tasks in the game
- Code editor
Create a CargoBay component with a <slot> tag and fallback content. Use it in App.vue by inserting your own content.
- Click in order
Arrange the code to use the CargoBay component with slot content:
- Code editor
Create an AlertBox component with a slot and fallback content 'No alerts'. Use it once with content and once without.
- Vertical ordering
Arrange the slot rendering stages from definition to display: