Vue.js course Β· Module 6: Components

Component Composition

5 min read
In this lesson6

Dr. Nova continues: "NOVA LAB station is not one monolithic block - it's a tree of modules: the main station contains sections, sections contain rooms, and rooms contain equipment. We build Vue applications the same way - from a hierarchy of components."

Imagine a command panel written in a single file of a thousand lines. The header, the module list, the control buttons and the footer are all mixed together, and a bug in one button has to be hunted down across the whole file. Such a monolith is hard to read, test and extend.

What Is Component Composition?

Composition is a pattern of building complex interfaces from simpler, smaller components. Instead of one huge component with thousands of lines of code, we divide the UI into logical parts, and the root component only arranges them:

1<!-- App.vue - root of the component tree -->
2<template>
3  <div class="station">
4    <StationHeader />
5    <main>
6      <ModuleList />
7      <ControlPanel />
8    </main>
9    <StationFooter />
10  </div>
11</template>
12
13<script setup>
14import StationHeader from './components/StationHeader.vue'
15import ModuleList from './components/ModuleList.vue'
16import ControlPanel from './components/ControlPanel.vue'
17import StationFooter from './components/StationFooter.vue'
18</script>

The App.vue template now reads like a list of the station's sections. The way components are used hasn't changed - these are the same imports and tags as in the previous lesson, just more of them.

Parent-Child Relationship

When a component uses another component in its template, a parent-child relationship is created. The module list displays three cards:

1<!-- ModuleList.vue (PARENT) -->
2<template>
3  <div class="module-list">
4    <h2>Station modules</h2>
5    <!-- ModuleCard is a CHILD of ModuleList -->
6    <ModuleCard />
7    <ModuleCard />
8    <ModuleCard />
9  </div>
10</template>
11
12<script setup>
13import ModuleCard from './ModuleCard.vue'
14</script>

In this example:

  • ModuleList is the parent
  • Each ModuleCard is a child
  • App is the parent of ModuleList - creating a hierarchy

The roles are relative: ModuleList is a child of App and at the same time the parent of the cards. If every card also displayed a ModulePanel component, it would be a grandchild of ModuleList, and App would become the cards' grandparent.

Component Tree

Every Vue application creates a component tree - a hierarchical structure starting from the root component (App.vue), which you'll also see in the Components tab of DevTools:

1App
2β”œβ”€β”€ StationHeader
3β”‚   β”œβ”€β”€ Logo
4β”‚   └── NavigationMenu
5β”‚       β”œβ”€β”€ NavItem (Dashboard)
6β”‚       β”œβ”€β”€ NavItem (Modules)
7β”‚       └── NavItem (Settings)
8β”œβ”€β”€ ModuleList
9β”‚   β”œβ”€β”€ ModuleCard (Oxygen Generator)
10β”‚   β”œβ”€β”€ ModuleCard (Plasma Core)
11β”‚   └── ModuleCard (Water Recycler)
12β”œβ”€β”€ ControlPanel
13β”‚   β”œβ”€β”€ StatusDisplay
14β”‚   └── ActionButtons
15└── StationFooter

The deeper in the tree, the more specialized the component. App is the general structure, while NavItem is a specific menu element.

Nesting Components

Components can contain other components to any depth. The control panel consists of a status display and three buttons:

1<!-- ControlPanel.vue -->
2<template>
3  <section class="control-panel">
4    <StatusDisplay />
5    <div class="actions">
6      <ActionButton label="Launch" icon="play" />
7      <ActionButton label="Stop" icon="stop" />
8      <ActionButton label="Restart" icon="refresh" />
9    </div>
10  </section>
11</template>
12
13<script setup>
14import StatusDisplay from './StatusDisplay.vue'
15import ActionButton from './ActionButton.vue'
16</script>

The status display is another level of nesting, because it is itself made of three separate indicators of the station's state:

1<!-- StatusDisplay.vue - another level of nesting -->
2<template>
3  <div class="status">
4    <StatusIndicator label="Energy" value="87%" color="green" />
5    <StatusIndicator label="Oxygen" value="95%" color="green" />
6    <StatusIndicator label="Temperature" value="22Β°C" color="yellow" />
7  </div>
8</template>
9
10<script setup>
11import StatusIndicator from './StatusIndicator.vue'
12</script>

The label, value and color attributes are data the parent passes to the child. For the child to receive them, it declares them with the defineProps macro. You don't import this macro - the <script setup> compiler knows it on its own. This is how a button from the control panel could look:

1<!-- ActionButton.vue -->
2<template>
3  <button class="action">{{ label }}</button>
4</template>
5
6<script setup>
7defineProps(['label', 'icon'])
8</script>

Each of the three buttons now shows its own label, even though there is only one component file. You'll learn the full power of props, with types and validation, at the Communication Station.

Three Steps to Use a Child

No matter how deep the tree goes, using a child component always takes the same three steps: you create a .vue file, import it in the parent and use its tag in the parent's template. If you skip the import, Vue won't recognize the tag.

Data Flow in the Tree

In the Vue component tree, data flows in specific directions:

  • Downward (parent β†’ child): via props - the parent passes data to children
  • Upward (child β†’ parent): via events - the child informs the parent about events

This pattern is called one-way data flow. You'll learn the details of props and events in the next module - for now remember the directions:

1      App (data)
2       β”‚
3       β–Ό  props (down)
4    ModuleList
5       β”‚
6       β–Ό  props (down)
7    ModuleCard
8       β”‚
9       β–²  events (up)
10    ModuleList
11       β”‚
12       β–²  events (up)
13      App (reacts)

A child doesn't change the parent's data directly - props are read-only for it - but reports an event, and the parent makes the decision. Thanks to that you always know where to look for the source of a change - in the component that owns the data. In the editor below you'll see the App, MarsBase, MarsModule and ModuleDetail tree, built exactly this way.

Remember: the station is a tree of modules - each component does one thing, the parent assembles its children into a whole, and data flows down while events flow up.

Code for this lesson: App.vue
1<script setup>
2import MarsBase from './MarsBase.vue'
3</script>
4
5<template>
6  <div class="composition-lab">
7    <div class="header">
8      <h1>Component Composition - NOVA LAB</h1>
9      <p class="subtitle">Building complex UI from components</p>
10    </div>
11
12    <div class="info-panel">
13      <h3>Component composition</h3>
14      <p>Components can be <strong>nested</strong> inside each other forming a hierarchy:</p>
15      <p>App &rarr; MarsBase &rarr; MarsModule &rarr; ModuleDetail</p>
16      <p>Each level is responsible for its own part of the interface</p>
17    </div>
18
19    <MarsBase />
20  </div>
21</template>
22
23<style scoped>
24.composition-lab {
25  background: linear-gradient(135deg, #0a0e27 0%, #1a1e3f 100%);
26  min-height: 100vh;
27  padding: 2rem;
28  color: #ffffff;
29  font-family: 'Courier New', monospace;
30}
31
32.header {
33  text-align: center;
34  margin-bottom: 2rem;
35  padding-bottom: 1rem;
36  border-bottom: 2px solid #00ff88;
37}
38
39.header h1 {
40  font-size: 2rem;
41  color: #00ff88;
42  text-shadow: 0 0 20px rgba(0, 255, 136, 0.5);
43  margin: 0;
44}
45
46.subtitle {
47  color: #00b4d8;
48  font-size: 1.1rem;
49  margin-top: 0.5rem;
50}
51
52.info-panel {
53  background: rgba(0, 180, 216, 0.1);
54  border: 2px solid #00b4d8;
55  border-radius: 10px;
56  padding: 1.5rem;
57  margin-bottom: 2rem;
58}
59
60.info-panel h3 {
61  color: #00b4d8;
62  margin-top: 0;
63}
64
65.info-panel strong {
66  color: #00ff88;
67}
68</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. 1. What is component composition?

  2. 2. What is the relationship between a component that uses another and the one being used?

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 MarsBase consisting of multiple MarsModule components.

  • Vertical ordering

    Arrange the component hierarchy from root to deepest:

  • Click in order

    Arrange the steps for using a child component:

Useful articles