Vue.js course · Module 1: Vue and Vite
Single File Components - System Modules
In this lesson5
At NOVA LAB, every ship module is a self-contained unit - it has its own power supply, logic, and housing. Similarly, a Vue Single File Component combines template, script, and style in a single file.
On a classic website, the look of a panel lives in an HTML file, its behavior in a JavaScript file, and its colors in a CSS stylesheet. To fix a single indicator you jump between three files, and a CSS rule written for one panel can accidentally repaint another. A Single File Component, SFC for short, packs the whole module into one file with the .vue extension.
Anatomy of a .vue Module
The browser can't open such a file - the Vue plugin configured in the Vite engine compiles it into plain JavaScript and CSS. Let's now take a life support panel apart. The three code blocks below sit in one file, one under another.
Three Module Sections
1. <template> - User Interface
- Defines what the astronaut sees
- Uses Vue syntax (directives, interpolation)
- Must be clear - in a crisis there's no time to search
The first section is the template: plain HTML enriched with Vue syntax, meaning mustaches and special attributes prefixed with a colon or an at sign:
1<template>
2 <!-- INTERFACE - what the astronaut sees -->
3 <div class="life-support-panel">
4 <h1>{{ systemName }}</h1>
5 <p>Oxygen level: {{ oxygenLevel }}%</p>
6 <div class="status" :class="statusClass">{{ status }}</div>
7 </div>
8</template>The mustaches display the system name, the oxygen level and the status, while :class="statusClass" attaches a class computed in the script to the element - we'll cover it in detail in the next lesson. The <div>, <h1> and <p> tags are still the same HTML you know from the first world.
2. <script setup> - System Logic
- Modern Composition API syntax
- Top-level variables, functions and imports are available in the template right away
- Reactivity with Vue
The second section holds the data and the logic. Besides the familiar ref(), it uses the computed() function, which we also import from the vue package:
1<script setup>
2// LOGIC - how the system works
3import { ref, computed } from 'vue'
4
5const systemName = ref('Life Support System')
6const oxygenLevel = ref(98.5)
7
8const status = computed(() =>
9 oxygenLevel.value > 90 ? 'OPTIMAL' : 'WARNING'
10)
11
12const statusClass = computed(() =>
13 oxygenLevel.value > 90 ? 'status-ok' : 'status-warning'
14)
15</script>The computed() function creates a computed value. You pass it an arrow function, and Vue remembers the result and recalculates it by itself whenever oxygenLevel changes. The oxygen level remains an ordinary ref container - status doesn't modify it, it only derives from it. In the script you read the result as status.value, and in the template {{ status }} is enough.
3. <style scoped> - Panel Appearance
- CSS specific to the module
scopedisolates styles - one module doesn't break another- Dark background, bright accents - space standard
The third section describes how the panel looks. The scoped attribute makes the rules written here apply only inside this component:
1<style scoped>
2/* APPEARANCE - how the panel looks */
3.life-support-panel {
4 background: #0a1628;
5 border: 2px solid #00ff88;
6 border-radius: 8px;
7 padding: 1.5rem;
8 color: #e0e0e0;
9}
10
11.status-ok { color: #00ff88; }
12.status-warning { color: #ffaa00; }
13</style>Vue adds a unique attribute such as data-v-7ba5bd90 to the component's elements and appends it to the selectors, so a .status-ok class from another module won't clash with this one. Without scoped, the same rules would apply to the whole page.
Section Order
Vue doesn't enforce the order of the blocks. The official style guide only recommends keeping it consistent across the project, with <style> last - that's why the create-vue wizard puts <script setup> at the top, while many teams start with <template>. At NOVA LAB we write in the order <template>, <script setup>, <style scoped>: first what the astronaut sees, then the wiring, and the housing last. A file may have at most one <template> and one <script setup>, but any number of <style> blocks.
A Function Behind a Button
A module that only shows readings is just half a system. In <script setup> you can also declare an ordinary function and attach it to a button in the template with the @click attribute. See it on the reactor cooling module:
1<template>
2 <p>Reactor temperature: {{ reactorTemp }}°C</p>
3 <p>State: {{ coolingStatus }}</p>
4 <button @click="coolDown">Cool down by 5°C</button>
5</template>
6
7<script setup>
8import { ref, computed } from 'vue'
9
10const reactorTemp = ref(95)
11
12const coolingStatus = computed(() => {
13 if (reactorTemp.value >= 90) return 'CRITICAL'
14 if (reactorTemp.value >= 70) return 'OVERHEATING'
15 return 'NORMAL'
16})
17
18function coolDown() {
19 reactorTemp.value -= 5
20}
21</script>A click runs coolDown, which lowers the temperature through .value. Vue notices the change, computed recalculates the state, and both paragraphs refresh by themselves. The arrow function in computed can have curly braces and several if statements - the first matching result is returned. We'll take event handling apart in detail in the next lesson.
<script setup> vs Old Syntax
The Composition API has two forms. The older one requires exporting an object with a setup() function, which at the end must return everything the template uses:
1<!-- NEW PROTOCOL (recommended) -->
2<script setup>
3import { ref } from 'vue'
4const pressure = ref(101.3)
5</script>
6
7<!-- OLD PROTOCOL (legacy) -->
8<script>
9import { ref } from 'vue'
10export default {
11 setup() {
12 const pressure = ref(101.3)
13 return { pressure }
14 }
15}
16</script><script setup> is syntactic sugar that the compiler turns into exactly this setup() function, so variables don't need a manual return. The older form hasn't been removed, and you'll come across it in older projects. At NOVA LAB we exclusively use the new protocol - it's shorter, faster at runtime and works better with TypeScript - and that's what I recommend to you from your very first file.
In the editor below, App.vue displays three SystemCard cards. Open components/SystemCard.vue and find all three sections in it. The card receives its data through defineProps - you'll learn this communication channel at the Communication Station.
Remember: a .vue file is a complete station module - the template is the panel, <script setup> is the wiring, and <style scoped> is the housing that doesn't let styles leak to the neighbors.
Code for this lesson: src/App.vue
1<template>
2 <div class="nova-lab">
3 <h1>Single File Components (SFC)</h1>
4
5 <section class="sfc-demo">
6 <h2>Structure of a .vue component</h2>
7 <div class="structure">
8 <div class="part template">
9 <h3><template></h3>
10 <p>HTML component structure</p>
11 </div>
12 <div class="part script">
13 <h3><script setup></h3>
14 <p>JavaScript/TypeScript logic</p>
15 </div>
16 <div class="part style">
17 <h3><style scoped></h3>
18 <p>CSS styles only for this component</p>
19 </div>
20 </div>
21 </section>
22
23 <section class="example">
24 <h2>Example: System Card</h2>
25 <SystemCard
26 name="Oxygen Generator Alpha"
27 status="operational"
28 :power="85"
29 />
30 <SystemCard
31 name="Water Recycler Beta"
32 status="warning"
33 :power="45"
34 />
35 <SystemCard
36 name="Plasma Reactor"
37 status="critical"
38 :power="12"
39 />
40 </section>
41 </div>
42</template>
43
44<script setup>
45import SystemCard from './components/SystemCard.vue'
46</script>
47
48<style>
49.nova-lab {
50 min-height: 100vh;
51 background: #0a0e27;
52 color: #e0e0e0;
53 padding: 2rem;
54 font-family: system-ui, sans-serif;
55}
56
57h1 {
58 color: #00ff88;
59 text-align: center;
60}
61
62section {
63 background: rgba(26, 30, 55, 0.8);
64 border: 1px solid #00b4d8;
65 border-radius: 12px;
66 padding: 1.5rem;
67 margin: 1.5rem auto;
68 max-width: 800px;
69}
70
71h2 {
72 color: #00b4d8;
73 margin-top: 0;
74}
75
76.structure {
77 display: flex;
78 flex-direction: column;
79 gap: 1rem;
80}
81
82.part {
83 padding: 1rem;
84 border-radius: 8px;
85}
86
87.part h3 {
88 margin: 0 0 0.5rem 0;
89 font-family: monospace;
90}
91
92.part p {
93 margin: 0;
94 font-size: 0.9rem;
95}
96
97.template { background: rgba(0, 255, 136, 0.2); border-left: 4px solid #00ff88; }
98.script { background: rgba(0, 180, 216, 0.2); border-left: 4px solid #00b4d8; }
99.style { background: rgba(255, 170, 0, 0.2); border-left: 4px solid #ffaa00; }
100
101.example {
102 display: flex;
103 flex-direction: column;
104 gap: 1rem;
105}
106</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 an SFC (Single File Component) in Vue.js?
2. What three sections does a .vue file consist of?
3. What does the 'scoped' attribute do in the <style scoped> section?
Hands-on tasks in the game
- Code editor
Complete a Single File Component with a template section (h1 heading), script setup (ref variable), and style scoped.
- Vertical ordering
Arrange the sections of a .vue file in the order used at NOVA LAB:
- Click in order
Arrange the correct structure of a script setup section with a reactive variable:
- Code editor
Create a system panel component with a template displaying data, script setup with ref() and computed(), and style scoped.