Vue.js course ยท Module 2: Template Syntax

Conditional Directives

5 min read
In this lesson4

Just as NOVA LAB systems make decisions based on sensor readings, Vue allows you to control what appears in the interface.

The classes from the previous lesson change how an element looks, but the element still exists. Sometimes the station hologram needs something stronger: show the panel of only the system the operator picked, and display the failure message only during a failure. That is what the conditional directives are for - v-if, which decides whether the element is created at all, and v-show, which only hides it.

v-if, v-else-if, v-else

v-if renders an element only when the expression is truthy. v-else-if adds further conditions, and v-else, which takes no value at all, catches the rest. Both must come immediately after an element with v-if or v-else-if. A <template> tag with v-if lets you put a whole group of elements under one condition:

1<template>
2  <div class="system-monitor" style="background: #0a0e27; color: #00ff88;">
3    <div v-if="systemType === 'oxygen'">
4      Oxygen generator - System active
5    </div>
6    <div v-else-if="systemType === 'water'">
7      Water recycling - System active
8    </div>
9    <div v-else-if="systemType === 'power'">
10      Plasma reactor - System active
11    </div>
12    <div v-else>
13      Unknown system
14    </div>
15
16    <!-- On a group of elements -->
17    <template v-if="showDetails">
18      <h2 style="color: #00b4d8;">{{ module.name }}</h2>
19      <p>{{ module.description }}</p>
20      <span>Status: {{ module.status }}</span>
21    </template>
22  </div>
23</template>

With systemType equal to 'oxygen', only the first div exists in the DOM. The others are not hidden - they simply are not there. The <template> itself is not rendered either: the heading, the paragraph and the span land on the page without an extra wrapper. If another element stood between v-if and v-else, the compiler would report that v-else has no adjacent v-if.

The conditions read ordinary data from the script:

1<script setup>
2import { ref, reactive } from 'vue'
3
4const systemType = ref('oxygen')
5const showDetails = ref(true)
6
7const module = reactive({
8  name: 'Life Support Alpha',
9  description: 'Primary oxygen generation system',
10  status: 'ONLINE'
11})
12</script>

Set systemType.value = 'water', and Vue removes the oxygen element and creates the water one. On every render Vue builds a new Virtual DOM tree, a lightweight description of the page in JavaScript. When the condition is false, the element is missing from that tree. Vue compares the new tree with the previous one and only then adds or removes the real element in the browser. v-if is also lazy: if the condition is false at the start, nothing is rendered, and on every toggle the block's content is destroyed and created from scratch.

v-show

v-show works differently: the element is always rendered, and the directive only toggles the CSS display property.

1<template>
2  <div class="alert-panel">
3    <!-- v-show - always rendered, just CSS display: none -->
4    <div v-show="isVisible" class="alert-message"
5         style="background: #ff4444; color: white; padding: 1rem;">
6      CRITICAL ALERT: Oxygen levels dropping!
7    </div>
8  </div>
9</template>
10
11<script setup>
12import { ref } from 'vue'
13
14const isVisible = ref(true)
15</script>

With isVisible set to false, the element gets style="display: none;", but it stays in the DOM together with its state. v-show does not work on a <template> tag and does not pair with v-else. Most often it is driven by a button that flips a boolean value:

1<template>
2  <button @click="isVisible = !isVisible">Toggle panel</button>
3  <div v-show="isVisible">Diagnostics panel</div>
4</template>

Every click turns true into false and back. It is the same in-template statement you met with v-on.

v-if vs v-show

Both directives produce the same effect on screen, but they cost different things:

Featurev-ifv-show
RenderingConditionalAlways
Initial costLowHigher
Toggle costHigherLow
Use caseRare changesFrequent toggling (e.g., alerts)

The Vue docs give the same advice I do: v-show for things toggled very often, like alerts or tabs, and v-if when the condition rarely changes or the content is heavy and not worth building in advance.

Practical Example

The mission panel combines two chains of conditions: a system filter and the data loading state. The first part of the template holds the filter buttons:

1<template>
2  <div class="mission-dashboard" style="background: #0a0e27; padding: 1rem;">
3    <div class="system-filter">
4      <button @click="filter = 'all'" class="btn-filter">All</button>
5      <button @click="filter = 'life-support'" class="btn-filter">Life Support</button>
6      <button @click="filter = 'power'" class="btn-filter">Power</button>
7    </div>
8
9    <div v-if="filter === 'all'" class="systems-grid">
10      All systems...
11    </div>
12    <div v-else-if="filter === 'life-support'" class="systems-grid">
13      Life support systems only...
14    </div>
15    <div v-else class="systems-grid">
16      Power systems only...
17    </div>

Each button assigns a new value to filter, and the chain shows exactly one grid. The second part of the same template handles loading the sensor data:

1    <!-- Loading state -->
2    <div v-if="isLoading" class="loader" style="color: #00b4d8;">
3      Loading sensor data...
4    </div>
5    <div v-else-if="error" class="error" style="color: #ff4444;">
6      {{ error }}
7    </div>
8    <div v-else class="content" style="color: #00ff88;">
9      {{ data }}
10    </div>
11  </div>
12</template>

The order matters: loading first, then the error, and the data last. Four refs drive the whole thing:

1<script setup>
2import { ref } from 'vue'
3
4const filter = ref('all')
5const isLoading = ref(false)
6const error = ref(null)
7const data = ref('All systems nominal')
8</script>

Set isLoading.value = true, and the loading message appears instead of the data. In the next lesson you will see v-for and learn why it is not combined with v-if on the same element.

Remember: v-if opens and closes the module hatch, so the element disappears from the DOM, while v-show only draws a curtain behind which the element keeps waiting.

Code for this lesson: App.vue
1<script setup>
2import { ref, computed } from 'vue'
3
4const oxygenLevel = ref(95)
5const temperature = ref(-55)
6const solarStorm = ref(false)
7const crewStatus = ref('all-safe')
8const showWarnings = ref(true)
9
10const alertType = computed(() => {
11  if (oxygenLevel.value < 50) return 'critical'
12  if (oxygenLevel.value < 80) return 'warning'
13  return 'safe'
14})
15
16function simulateStorm() {
17  solarStorm.value = true
18  oxygenLevel.value = 45
19  temperature.value = -80
20  crewStatus.value = 'emergency'
21}
22
23function normalizeConditions() {
24  solarStorm.value = false
25  oxygenLevel.value = 95
26  temperature.value = -55
27  crewStatus.value = 'all-safe'
28}
29
30function adjustOxygen(value) {
31  oxygenLevel.value = Math.max(0, Math.min(100, oxygenLevel.value + value))
32}
33</script>
34
35<template>
36  <div class="conditions-monitor">
37    <h1>Conditions Monitor - Mars Base NOVA</h1>
38
39    <!-- v-if, v-else-if, v-else -->
40    <div class="alert-section">
41      <div v-if="alertType === 'critical'" class="alert alert-critical">
42        CRITICAL ALARM! Oxygen level below safe level!
43      </div>
44      <div v-else-if="alertType === 'warning'" class="alert alert-warning">
45        WARNING: Oxygen level has dropped below normal.
46      </div>
47      <div v-else class="alert alert-safe">
48        All systems operating normally.
49      </div>
50    </div>
51
52    <!-- v-show vs v-if -->
53    <div class="info-panel">
54      <h2>Difference between v-if and v-show</h2>
55      <button @click="showWarnings = !showWarnings" class="btn btn-toggle">
56        Toggle Warnings (v-show)
57      </button>
58
59      <!-- v-show: element stays in DOM, only hidden via CSS -->
60      <div v-show="showWarnings" class="warning-box">
61        <p>This warning is controlled by v-show</p>
62        <p>Element stays in DOM, but is hidden via display: none</p>
63      </div>
64
65      <!-- v-if: element is removed/added from DOM -->
66      <div v-if="showWarnings" class="warning-box">
67        <p>This warning is controlled by v-if</p>
68        <p>Element is completely removed/added from DOM</p>
69      </div>
70    </div>
71
72    <div class="status-grid">
73      <!-- Conditional rendering of the storm status -->
74      <div class="status-card" :class="{ 'storm-active': solarStorm }">
75        <h3>Solar Storm</h3>
76        <div v-if="solarStorm" class="storm-warning">
77          <p class="storm-icon">!!!</p>
78          <p>ACTIVE SOLAR STORM</p>
79          <p>All external operations suspended</p>
80        </div>
81        <div v-else class="storm-calm">
82          <p class="calm-icon">OK</p>
83          <p>Stable conditions</p>
84        </div>
85      </div>
86
87      <!-- Crew status with multiple conditions -->
88      <div class="status-card">
89        <h3>Crew Status</h3>
90        <div v-if="crewStatus === 'emergency'" class="crew-emergency">
91          EMERGENCY - Everyone in the shelter
92        </div>
93        <div v-else-if="crewStatus === 'alert'" class="crew-alert">
94          Elevated Alert State
95        </div>
96        <div v-else-if="crewStatus === 'all-safe'" class="crew-safe">
97          All crew safe - Routine operations
98        </div>
99        <div v-else class="crew-unknown">
100          Status unknown
101        </div>
102      </div>
103
104      <!-- Oxygen level control -->
105      <div class="status-card">
106        <h3>Oxygen Level</h3>
107        <div class="oxygen-display" :class="'oxygen-' + alertType">
108          {{ oxygenLevel }}%
109        </div>
110        <div class="oxygen-controls">
111          <button @click="adjustOxygen(-10)" class="btn btn-small">-10%</button>
112          <button @click="adjustOxygen(10)" class="btn btn-small">+10%</button>
113        </div>
114      </div>
115
116      <!-- Temperature -->
117      <div class="status-card">
118        <h3>Temperature</h3>
119        <div class="temp-display">{{ temperature }}ยฐC</div>
120        <div v-if="temperature < -70" class="temp-warning">
121          Extremely low temperatures
122        </div>
123      </div>
124    </div>
125
126    <div class="controls-section">
127      <h2>Conditions Simulation</h2>
128      <div class="button-group">
129        <button @click="simulateStorm" class="btn btn-danger">
130          Simulate Solar Storm
131        </button>
132        <button @click="normalizeConditions" class="btn btn-success">
133          Restore Normal Conditions
134        </button>
135      </div>
136    </div>
137
138    <div class="info-box">
139      <h3>Conditional Rendering in Vue</h3>
140      <ul>
141        <li><strong>v-if</strong> - Renders the element only when condition is true (removes from DOM)</li>
142        <li><strong>v-else-if</strong> - Additional conditions in the chain</li>
143        <li><strong>v-else</strong> - Default case</li>
144        <li><strong>v-show</strong> - Always in DOM, hides via CSS (better for frequent changes)</li>
145      </ul>
146    </div>
147  </div>
148</template>
149
150<style scoped>
151.conditions-monitor {
152  background: linear-gradient(135deg, #0a0e27 0%, #1a1e3f 100%);
153  min-height: 100vh;
154  padding: 2rem;
155  color: #ffffff;
156  font-family: 'Courier New', monospace;
157}
158
159h1 {
160  text-align: center;
161  color: #00ff88;
162  text-shadow: 0 0 20px rgba(0, 255, 136, 0.5);
163  margin-bottom: 2rem;
164}
165
166.alert-section {
167  margin-bottom: 2rem;
168}
169
170.alert {
171  padding: 1.5rem;
172  border-radius: 10px;
173  font-size: 1.2rem;
174  font-weight: bold;
175  text-align: center;
176  animation: slideIn 0.5s ease;
177}
178
179@keyframes slideIn {
180  from { transform: translateY(-20px); opacity: 0; }
181  to { transform: translateY(0); opacity: 1; }
182}
183
184.alert-critical {
185  background: rgba(255, 0, 85, 0.2);
186  border: 3px solid #ff0055;
187  animation: pulse 1s infinite;
188}
189
190.alert-warning {
191  background: rgba(255, 180, 0, 0.2);
192  border: 3px solid #ffb400;
193}
194
195.alert-safe {
196  background: rgba(0, 255, 136, 0.2);
197  border: 3px solid #00ff88;
198}
199
200.info-panel {
201  background: rgba(0, 180, 216, 0.1);
202  border: 2px solid #00b4d8;
203  border-radius: 10px;
204  padding: 1.5rem;
205  margin-bottom: 2rem;
206}
207
208.warning-box {
209  background: rgba(255, 180, 0, 0.1);
210  border: 2px solid #ffb400;
211  border-radius: 5px;
212  padding: 1rem;
213  margin-top: 1rem;
214}
215
216.status-grid {
217  display: grid;
218  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
219  gap: 1.5rem;
220  margin-bottom: 2rem;
221}
222
223.status-card {
224  background: rgba(0, 255, 136, 0.1);
225  border: 2px solid #00ff88;
226  border-radius: 10px;
227  padding: 1.5rem;
228  transition: all 0.3s ease;
229}
230
231.status-card.storm-active {
232  border-color: #ff0055;
233  background: rgba(255, 0, 85, 0.2);
234  animation: shake 0.5s infinite;
235}
236
237@keyframes shake {
238  0%, 100% { transform: translateX(0); }
239  25% { transform: translateX(-5px); }
240  75% { transform: translateX(5px); }
241}
242
243.status-card h3 {
244  color: #00b4d8;
245  margin-top: 0;
246}
247
248.storm-warning, .storm-calm {
249  text-align: center;
250}
251
252.storm-icon {
253  font-size: 3rem;
254  margin: 0;
255  animation: flicker 0.3s infinite;
256}
257
258@keyframes flicker {
259  0%, 100% { opacity: 1; }
260  50% { opacity: 0.5; }
261}
262
263.calm-icon {
264  font-size: 3rem;
265  margin: 0;
266}
267
268.crew-emergency {
269  color: #ff0055;
270  font-weight: bold;
271  padding: 1rem;
272  background: rgba(255, 0, 85, 0.2);
273  border-radius: 5px;
274}
275
276.crew-alert {
277  color: #ffb400;
278  font-weight: bold;
279}
280
281.crew-safe {
282  color: #00ff88;
283  font-weight: bold;
284}
285
286.oxygen-display {
287  font-size: 3rem;
288  text-align: center;
289  font-weight: bold;
290  margin: 1rem 0;
291}
292
293.oxygen-critical {
294  color: #ff0055;
295  animation: pulse 1s infinite;
296}
297
298.oxygen-warning {
299  color: #ffb400;
300}
301
302.oxygen-safe {
303  color: #00ff88;
304}
305
306.oxygen-controls {
307  display: flex;
308  gap: 1rem;
309  justify-content: center;
310}
311
312.temp-display {
313  font-size: 2.5rem;
314  text-align: center;
315  color: #00b4d8;
316  margin: 1rem 0;
317}
318
319.temp-warning {
320  color: #00b4d8;
321  text-align: center;
322  font-size: 0.9rem;
323}
324
325.controls-section {
326  background: rgba(255, 255, 255, 0.05);
327  border: 2px solid #00ff88;
328  border-radius: 10px;
329  padding: 1.5rem;
330  margin-bottom: 2rem;
331}
332
333.button-group {
334  display: flex;
335  gap: 1rem;
336  justify-content: center;
337  flex-wrap: wrap;
338}
339
340.btn {
341  padding: 1rem 2rem;
342  border: none;
343  border-radius: 5px;
344  font-size: 1rem;
345  font-weight: bold;
346  cursor: pointer;
347  transition: all 0.3s ease;
348  font-family: 'Courier New', monospace;
349}
350
351.btn-toggle {
352  background: #00b4d8;
353  color: #ffffff;
354}
355
356.btn-danger {
357  background: #ff0055;
358  color: #ffffff;
359}
360
361.btn-success {
362  background: #00ff88;
363  color: #0a0e27;
364}
365
366.btn-small {
367  padding: 0.5rem 1rem;
368  font-size: 0.9rem;
369  background: #00b4d8;
370  color: #ffffff;
371}
372
373.btn:hover {
374  transform: scale(1.05);
375  box-shadow: 0 5px 15px rgba(0, 255, 136, 0.4);
376}
377
378.info-box {
379  background: rgba(0, 255, 136, 0.05);
380  border: 2px solid #00ff88;
381  border-radius: 10px;
382  padding: 1.5rem;
383}
384
385.info-box h3 {
386  color: #00ff88;
387  margin-top: 0;
388}
389
390.info-box ul {
391  list-style: none;
392  padding: 0;
393}
394
395.info-box li {
396  padding: 0.5rem 0;
397  color: #ffffff;
398  border-left: 3px solid #00b4d8;
399  padding-left: 1rem;
400  margin: 0.5rem 0;
401}
402
403.info-box strong {
404  color: #00b4d8;
405}
406</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 the difference between v-if and v-show?

  2. 2. When is it better to use v-show instead of v-if?

  3. 3. Can you use v-else-if in Vue?

  4. 4. Which code snippet correctly uses v-if with v-else?

  5. 5. What happens to a DOM element when the v-if condition is set to false?

Hands-on tasks in the game

  • Code editor

    Use v-if, v-else-if, and v-else for conditional rendering of system statuses.

  • Vertical ordering

    Arrange the correct conditional chain from v-if to v-else:

  • Vertical ordering

    Order the steps of v-if from condition check to rendering:

  • Click in order

    Arrange the syntax: first the toggle button, then the element with v-show below it:

  • Code editor

    Use v-show with a toggle button to hide/show sections.

Useful articles