Vue.js course Β· Module 6: Components

Components - Base Building Modules

5 min read
In this lesson5

Welcome to the NOVA LAB 3D Printer Bay! Dr. Nova says: just as the 3D printer creates modular parts of the Martian habitat, you'll learn to create Vue components - reusable application elements.

Until now, the whole panel lived in a single App.vue file. That's enough for one screen, but the base keeps growing: five identical module cards mean five copies of the same HTML, and a fix in one card means fixing all five. A component solves this problem - you design an element once and use it as many times as you like.

What you will learn

  • how to create components and name them by convention,
  • how to import them locally and register them globally,
  • how to build an interface from a tree of components,
  • how to run code in the successive phases of a component's life,
  • how to isolate styles and extract shared logic into composables.

What is a Component?

A component is a reusable, self-contained UI unit with its own template, logic, and styles - like a habitat module that can be connected with others. Here is a module card saved in the ModuleCard.vue file, starting with the template and the data:

1<!-- ModuleCard.vue -->
2<template>
3  <article class="module-card" style="background: #0a0e27; border: 1px solid #00b4d8; color: #00ff88;">
4    <div class="module-icon">β—†</div>
5    <h3>{{ moduleName }}</h3>
6    <p>{{ moduleType }}</p>
7    <p class="status">{{ status }}</p>
8  </article>
9</template>
10
11<script setup>
12import { ref } from 'vue'
13
14const moduleName = ref('Oxygen Generator Alpha')
15const moduleType = ref('Life Support')
16const status = ref('ACTIVE')
17</script>

The template shows the module's name, type and status, and three ref() containers hold this data - the same syntax you used in the Launch Zone. The only new thing is that the file has its own name and will be used somewhere else. We write the card's housing in the same file, in a section with styles limited to the component:

1<style scoped>
2.module-card {
3  border-radius: 8px;
4  padding: 1rem;
5  max-width: 300px;
6}
7
8.module-icon {
9  font-size: 3rem;
10  text-align: center;
11}
12
13.status {
14  color: #00ff88;
15  font-weight: bold;
16}
17</style>

We arrange the sections in the order used at NOVA LAB since the Launch Zone: <template>, <script setup>, <style scoped>. In the editor you'll also see files with <script setup> at the top - that's correct too, because Vue only requires consistency within a project and the style last.

Using a Component

To print the card in the base, we import the file in App.vue and use the import name like an HTML tag:

1<!-- App.vue -->
2<template>
3  <div class="production-bay" style="background: #0a0e27;">
4    <ModuleCard />
5    <ModuleCard />
6    <ModuleCard />
7  </div>
8</template>
9
10<script setup>
11import ModuleCard from './components/ModuleCard.vue'
12</script>

Every <ModuleCard /> tag creates a separate instance of the component with its own data, so changing the status in one card won't touch the others. The ModuleCard.vue file hasn't changed and won't change no matter how many copies you place. For now all three cards show the same data - passing different values to each card, known as props, is something you'll learn at the Communication Station. This is the main advantage of a component architecture: you write the code once, reuse it many times, and make a fix in one place.

Component Naming

We write component names in PascalCase, meaning every word starts with a capital letter, with no spaces or underscores. We name the files the same way: ModuleCard.vue, not module_card.vue or moduleCard.vue:

1<script setup>
2// PascalCase - recommended
3import ArtworkCard from './ArtworkCard.vue'
4import UserProfile from './UserProfile.vue'
5import NavigationMenu from './NavigationMenu.vue'
6
7// In template - PascalCase or kebab-case
8</script>
9
10<template>
11  <!-- PascalCase -->
12  <ArtworkCard />
13  <UserProfile />
14
15  <!-- or kebab-case -->
16  <artwork-card />
17  <user-profile />
18</template>

Both tag forms work in .vue file templates, but I recommend PascalCase - you can see at a glance that <UserProfile /> is a component and <div> is plain HTML. A second rule from the official style guide says a component name should have at least two words. All HTML elements have single-word names, so a Card component could clash with a future browser tag, while ModuleCard never will. The exception is the root App component, and base components get a prefix, for example BaseButton.

File Organization

As the number of components grows, we group them in subfolders of src/components/ according to the role they play in the base:

1src/
2β”œβ”€β”€ components/
3β”‚   β”œβ”€β”€ common/
4β”‚   β”‚   β”œβ”€β”€ BaseButton.vue
5β”‚   β”‚   β”œβ”€β”€ BasePanelCard.vue
6β”‚   β”‚   └── BaseInput.vue
7β”‚   β”œβ”€β”€ modules/
8β”‚   β”‚   β”œβ”€β”€ ModuleCard.vue
9β”‚   β”‚   β”œβ”€β”€ ModuleList.vue
10β”‚   β”‚   └── ModuleDetail.vue
11β”‚   └── layout/
12β”‚       β”œβ”€β”€ MissionHeader.vue
13β”‚       β”œβ”€β”€ ControlFooter.vue
14β”‚       └── NavSidebar.vue

The common/ folder holds base components, modules/ the module cards and lists, and layout/ the elements of the screen frame. Reusable components always go to src/components/, while whole screens connected to navigation go to the src/views/ folder you know from the project structure.

In the editor below, App.vue uses the PrinterStatusPanel component three times. You'll see defineProps and defineEmits in it - these are the communication channels of the Communication Station, and for today it's enough that the panel is a separate file, imported and used like a tag.

Remember: a component is a printed habitat module - you design it once, in a file with a multi-word PascalCase name, and mount it in the base as many times as you need.

Code for this lesson: App.vue
1<script setup>
2import { ref } from 'vue'
3import PrinterStatusPanel from './PrinterStatusPanel.vue'
4
5const printers = ref([
6  {
7    id: 'p1',
8    name: '3D Printer Alpha',
9    status: 'printing',
10    progress: 67,
11    material: 'PLA Composite',
12    temperature: 210
13  },
14  {
15    id: 'p2',
16    name: '3D Printer Beta',
17    status: 'idle',
18    progress: 0,
19    material: 'Carbon Fiber',
20    temperature: 25
21  },
22  {
23    id: 'p3',
24    name: '3D Printer Gamma',
25    status: 'error',
26    progress: 45,
27    material: 'Titanium Alloy',
28    temperature: 185
29  }
30])
31
32function handlePrinterAction(printerId, action) {
33  const printer = printers.value.find(p => p.id === printerId)
34  if (printer) {
35    console.log(`Action ${action} on ${printer.name}`)
36    if (action === 'start') {
37      printer.status = 'printing'
38    } else if (action === 'stop') {
39      printer.status = 'idle'
40      printer.progress = 0
41    } else if (action === 'pause') {
42      printer.status = 'paused'
43    }
44  }
45}
46</script>
47
48<template>
49  <div class="printing-lab">
50    <div class="header">
51      <h1>3D Print Lab - NOVA LAB</h1>
52      <p class="subtitle">3D printer control panel on Mars</p>
53    </div>
54
55    <div class="info-panel">
56      <h3>Defining components with props and emits</h3>
57      <p>Components receive data through <strong>props</strong> and communicate through <strong>emits</strong></p>
58    </div>
59
60    <div class="printers-grid">
61      <PrinterStatusPanel
62        v-for="printer in printers"
63        :key="printer.id"
64        :printer-id="printer.id"
65        :printer-name="printer.name"
66        :status="printer.status"
67        :progress="printer.progress"
68        :material="printer.material"
69        :temperature="printer.temperature"
70        @action="handlePrinterAction"
71      />
72    </div>
73  </div>
74</template>
75
76<style scoped>
77.printing-lab {
78  background: linear-gradient(135deg, #0a0e27 0%, #1a1e3f 100%);
79  min-height: 100vh;
80  padding: 2rem;
81  color: #ffffff;
82  font-family: 'Courier New', monospace;
83}
84
85.header {
86  text-align: center;
87  margin-bottom: 2rem;
88  padding-bottom: 1rem;
89  border-bottom: 2px solid #00ff88;
90}
91
92.header h1 {
93  font-size: 2.5rem;
94  color: #00ff88;
95  text-shadow: 0 0 20px rgba(0, 255, 136, 0.5);
96  margin: 0;
97}
98
99.subtitle {
100  color: #00b4d8;
101  font-size: 1.2rem;
102  margin-top: 0.5rem;
103}
104
105.info-panel {
106  background: rgba(0, 180, 216, 0.1);
107  border: 2px solid #00b4d8;
108  border-radius: 10px;
109  padding: 1.5rem;
110  margin-bottom: 2rem;
111}
112
113.info-panel h3 {
114  color: #00b4d8;
115  margin-top: 0;
116}
117
118.info-panel strong {
119  color: #00ff88;
120}
121
122.printers-grid {
123  display: grid;
124  grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
125  gap: 1.5rem;
126}
127</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 a component in Vue.js?

  2. 2. What is the main advantage of component-based architecture in Vue?

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 MarsModule.vue component with template, script setup, and style scoped sections.

  • Click in order

    Arrange the sections of a Single File Component in the order used at NOVA LAB:

  • Code editor

    Create a StatusPanel.vue component with ref() for status and temperature displayed in the template.

Useful articles