Vue.js course Β· Module 2: Template Syntax

Vue Directives

6 min read
In this lesson4

Directives are special attributes starting with v-. Each directive is like an instruction for the Mars base control system.

The mustaches from the previous lesson have two gaps. They cannot reach attributes, so a sensor image URL or a disabled button cannot depend on the data. And they cannot hear the operator: clicking the hologram triggers nothing. Directives close both gaps. Their structure is always the same: a name after the v- prefix, an argument after a colon, modifiers after dots and an expression in quotes. In v-on:submit.prevent="onSubmit" the name is on, the argument is submit, the modifier is prevent, and the value is the onSubmit function.

v-bind - Attribute Binding

v-bind keeps an attribute in sync with the data: after the colon you give the attribute name, and in quotes an expression. Since this directive appears all the time, it has a shorthand - just the colon. Here is a panel template with four typical cases:

1<template>
2  <div class="control-panel" style="background: #0a0e27; padding: 1rem;">
3    <!-- Full syntax -->
4    <img v-bind:src="sensorImage" v-bind:alt="sensorName">
5
6    <!-- Shorthand syntax (recommended) -->
7    <img :src="sensorImage" :alt="sensorName">
8
9    <!-- Dynamic attributes -->
10    <a :href="documentationUrl" :class="linkClass">Documentation</a>
11
12    <!-- Boolean attributes -->
13    <button :disabled="isProcessing" class="btn-neon">Launch</button>
14  </div>
15</template>

Both images produce identical HTML - the shorthand changes nothing but the length of the code. The quotes hold an expression, not text: :alt="sensorName" inserts the variable's value, while a plain alt="sensorName" would write the word sensorName literally. The boolean attribute disabled appears when the value is truthy and disappears for false. When a binding evaluates to null or undefined, Vue removes the attribute entirely.

The data for these bindings are ordinary refs from the script section - nothing new compared with module 1:

1<script setup>
2import { ref } from 'vue'
3
4const sensorImage = ref('/images/oxygen-sensor.png')
5const sensorName = ref('Oxygen Sensor MK-7')
6const documentationUrl = ref('https://nova-lab.mars/docs')
7const linkClass = ref('neon-link')
8const isProcessing = ref(false)
9</script>

Setting isProcessing.value = true is enough for the button to get the disabled attribute - you do not touch the template. The button's look comes from ordinary CSS, because v-bind does not change how styles work:

1<style>
2.btn-neon {
3  background: transparent;
4  border: 2px solid #00ff88;
5  color: #00ff88;
6  padding: 0.5rem 1rem;
7  cursor: pointer;
8}
9.btn-neon:hover {
10  background: #00ff8822;
11  box-shadow: 0 0 10px #00ff88;
12}
13</style>

There is not a single Vue feature in the style section. My advice: always write the : shorthand - in a dense template you immediately see which attributes are alive. Since Vue 3.4 an even shorter form works: when the attribute and the variable share a name, :id means the same as :id="id".

v-on - Event Handling

v-on attaches a function to a browser event; its shorthand is the at sign @. The value can be a function name or a short statement, for example a call with an argument. The special $event variable passes the event object when you call the function yourself:

1<template>
2  <div class="reactor-controls" style="background: #0a0e27; color: #00b4d8;">
3    <!-- Full syntax -->
4    <button v-on:click="handleActivation">Activate</button>
5
6    <!-- Shorthand syntax (recommended) -->
7    <button @click="handleActivation" class="btn-reactor">Activate Reactor</button>
8
9    <!-- Inline handler -->
10    <button @click="powerLevel++">Increase Power</button>
11
12    <!-- With argument -->
13    <button @click="sendSignal('ALPHA-BASE')">Send Signal</button>
14
15    <!-- With event object -->
16    <button @click="handleActivation($event)">Event Info</button>
17  </div>
18</template>

The compiler recognizes what you gave it: the bare name handleActivation means Vue calls the function and passes the native event as its only argument. A form with parentheses is a statement, so the event has to be passed explicitly through $event. There is no .value in powerLevel++, because refs are unwrapped in the template.

The handler functions live in the script, where .value applies again:

1<script setup>
2import { ref } from 'vue'
3
4const powerLevel = ref(0)
5
6function handleActivation(event) {
7  console.log('Reactor activated!', event)
8}
9
10function sendSignal(baseCode) {
11  alert(`Sending signal to: ${baseCode}`)
12}
13</script>

The button is still an ordinary <button> - v-on only adds an event listener to it, just as addEventListener would.

Event Modifiers

Calls like event.preventDefault() or event.stopPropagation() clutter functions with DOM details. A modifier added after a dot takes care of that in the template. The cheat sheet below shows only the opening tags:

1<template>
2  <div class="mission-control">
3    <!-- Prevent default -->
4    <form @submit.prevent="onSubmit" class="mission-form">
5
6    <!-- Stop propagation -->
7    <div @click.stop="onClick" class="stop-zone">
8
9    <!-- Only once -->
10    <button @click.once="launchSequence" class="btn-launch">Start Sequence</button>
11
12    <!-- Capture mode -->
13    <div @click.capture="onCapture" class="capture-zone">
14
15    <!-- Self only -->
16    <div @click.self="onSelf" class="self-zone">
17
18    <!-- Combination -->
19    <a @click.stop.prevent="onClick" class="safe-link">
20  </div>
21</template>

.prevent calls event.preventDefault(), so the form does not reload the page. .stop calls event.stopPropagation(), and the event does not reach the parents. .once lets the handler fire at most once, .capture handles the event in the capture phase, before it reaches the child, and .self reacts only when the element itself was clicked. When the event arrives, Vue applies the modifiers first and only then calls your function. In a chain the order matters: @click.prevent.self blocks the default action for the children too, while @click.self.prevent does it only for the element itself.

Key Modifiers

The keyup and keydown events have modifiers too. Vue knows the aliases .enter, .tab, .delete (Delete and Backspace), .esc, .space and the arrows, and the system keys are .ctrl, .alt, .shift and .meta. The functions in the example are defined in the script:

1<template>
2  <input @keyup.enter="sendCommand" placeholder="Command for the station">
3  <input @keyup.esc="clearCommand" placeholder="Esc clears the field">
4  <button @click.ctrl="emergencyStop">Ctrl + click: emergency stop</button>
5</template>

sendCommand runs only after Enter, clearCommand only after Escape, and the emergency stop requires Ctrl to be held down. Other keys trigger nothing - without a single if checking event.key. In the next lesson you will see that :class and :style are special kinds of v-bind that accept objects and arrays.

Remember: the colon connects an attribute to data, the at sign connects an event to a function, and the dot adds a safety protocol to the command.

Code for this lesson: App.vue
1<script setup>
2import { ref } from 'vue'
3
4const stationStatus = ref('operational')
5const powerLevel = ref(85)
6const alertLevel = ref('low')
7const showDetails = ref(false)
8const logoUrl = ref('https://placehold.co/100/00ff88/0a0e27?text=NOVA')
9
10const systemMessages = ref([
11  { id: 1, text: 'Life support systems: ONLINE', type: 'success' },
12  { id: 2, text: 'Energy level: STABLE', type: 'info' },
13  { id: 3, text: 'Communication with Earth: ACTIVE', type: 'success' }
14])
15
16function toggleDetails() {
17  showDetails.value = !showDetails.value
18}
19
20function increasePower() {
21  if (powerLevel.value < 100) {
22    powerLevel.value += 5
23  }
24}
25
26function decreasePower() {
27  if (powerLevel.value > 0) {
28    powerLevel.value -= 5
29  }
30}
31</script>
32
33<template>
34  <div class="control-panel">
35    <div class="station-header">
36      <!-- v-bind for src and alt attributes -->
37      <img :src="logoUrl" :alt="'NOVA LAB Logo'" class="logo">
38      <h1>NOVA Research Station</h1>
39    </div>
40
41    <div class="power-control">
42      <h2>Energy Control</h2>
43      <!-- v-bind for class and style -->
44      <div
45        class="power-bar"
46        :class="{ 'critical': powerLevel < 30, 'optimal': powerLevel >= 70 }"
47        :style="{ width: powerLevel + '%' }"
48      >
49        {{ powerLevel }}%
50      </div>
51
52      <!-- v-on (@) for event handling -->
53      <div class="controls">
54        <button @click="decreasePower" class="btn btn-danger">-5%</button>
55        <button @click="increasePower" class="btn btn-success">+5%</button>
56      </div>
57    </div>
58
59    <div class="status-section">
60      <h2>Systems Status</h2>
61      <button @click="toggleDetails" class="btn btn-info">
62        {{ showDetails ? 'Hide details' : 'Show details' }}
63      </button>
64
65      <div v-show="showDetails" class="messages">
66        <div
67          v-for="msg in systemMessages"
68          :key="msg.id"
69          :class="['message', 'message-' + msg.type]"
70        >
71          {{ msg.text }}
72        </div>
73      </div>
74    </div>
75
76    <div class="info-box">
77      <h3>Vue.js directives</h3>
78      <ul>
79        <li><strong>v-bind</strong> (shorthand :) - attribute binding</li>
80        <li><strong>v-on</strong> (shorthand @) - event handling</li>
81        <li><strong>:class</strong> - dynamic CSS classes</li>
82        <li><strong>:style</strong> - dynamic inline styles</li>
83      </ul>
84    </div>
85  </div>
86</template>
87
88<style scoped>
89.control-panel {
90  background: linear-gradient(135deg, #0a0e27 0%, #1a1e3f 100%);
91  min-height: 100vh;
92  padding: 2rem;
93  color: #ffffff;
94  font-family: 'Courier New', monospace;
95}
96
97.station-header {
98  text-align: center;
99  margin-bottom: 2rem;
100}
101
102.logo {
103  width: 100px;
104  height: 100px;
105  border-radius: 50%;
106  border: 3px solid #00ff88;
107  box-shadow: 0 0 20px rgba(0, 255, 136, 0.5);
108}
109
110h1, h2, h3 {
111  color: #00ff88;
112  text-shadow: 0 0 10px rgba(0, 255, 136, 0.5);
113}
114
115.power-control {
116  background: rgba(0, 255, 136, 0.1);
117  border: 2px solid #00ff88;
118  border-radius: 10px;
119  padding: 1.5rem;
120  margin-bottom: 2rem;
121}
122
123.power-bar {
124  height: 40px;
125  background: linear-gradient(90deg, #00ff88, #00b4d8);
126  border-radius: 20px;
127  display: flex;
128  align-items: center;
129  justify-content: center;
130  font-weight: bold;
131  transition: all 0.3s ease;
132  margin: 1rem 0;
133}
134
135.power-bar.critical {
136  background: linear-gradient(90deg, #ff0055, #ff6600);
137  animation: pulse 1s infinite;
138}
139
140.power-bar.optimal {
141  box-shadow: 0 0 20px rgba(0, 255, 136, 0.8);
142}
143
144@keyframes pulse {
145  0%, 100% { opacity: 1; }
146  50% { opacity: 0.7; }
147}
148
149.controls {
150  display: flex;
151  gap: 1rem;
152  justify-content: center;
153  margin-top: 1rem;
154}
155
156.btn {
157  padding: 0.75rem 1.5rem;
158  border: none;
159  border-radius: 5px;
160  font-size: 1rem;
161  font-weight: bold;
162  cursor: pointer;
163  transition: all 0.3s ease;
164  font-family: 'Courier New', monospace;
165}
166
167.btn-success {
168  background: #00ff88;
169  color: #0a0e27;
170}
171
172.btn-danger {
173  background: #ff0055;
174  color: #ffffff;
175}
176
177.btn-info {
178  background: #00b4d8;
179  color: #ffffff;
180}
181
182.btn:hover {
183  transform: scale(1.05);
184  box-shadow: 0 5px 15px rgba(0, 255, 136, 0.4);
185}
186
187.status-section {
188  background: rgba(0, 180, 216, 0.1);
189  border: 2px solid #00b4d8;
190  border-radius: 10px;
191  padding: 1.5rem;
192  margin-bottom: 2rem;
193}
194
195.messages {
196  margin-top: 1rem;
197}
198
199.message {
200  padding: 1rem;
201  margin: 0.5rem 0;
202  border-radius: 5px;
203  border-left: 4px solid;
204}
205
206.message-success {
207  background: rgba(0, 255, 136, 0.2);
208  border-color: #00ff88;
209}
210
211.message-info {
212  background: rgba(0, 180, 216, 0.2);
213  border-color: #00b4d8;
214}
215
216.info-box {
217  background: rgba(255, 255, 255, 0.05);
218  border: 2px solid #00ff88;
219  border-radius: 10px;
220  padding: 1.5rem;
221}
222
223.info-box ul {
224  list-style: none;
225  padding: 0;
226}
227
228.info-box li {
229  padding: 0.5rem 0;
230  color: #ffffff;
231}
232
233.info-box strong {
234  color: #00b4d8;
235}
236</style>

The second editor is a modifiers lab: submit its form with .prevent, click the buttons with .stop and .once, and try Enter, Escape and the Ctrl+S shortcut in the text fields.

Code for this lesson: App.vue
1<script setup>
2import { ref } from 'vue'
3
4const commandInput = ref('')
5const searchQuery = ref('')
6const formData = ref({
7  mission: '',
8  priority: 'medium',
9  description: ''
10})
11
12const logs = ref([])
13const systemMessages = ref([
14  { id: 1, text: 'System initialized' },
15  { id: 2, text: 'Connection established with Mars Base' },
16  { id: 3, text: 'All systems operational' }
17])
18let nextMessageId = 4
19
20function handleCommand(event) {
21  event.preventDefault()
22  if (commandInput.value.trim()) {
23    logs.value.unshift({
24      id: Date.now(),
25      command: commandInput.value,
26      timestamp: new Date().toLocaleTimeString()
27    })
28    commandInput.value = ''
29  }
30}
31
32function preventRefresh(event) {
33  event.preventDefault()
34  alert('Form was stopped by event.preventDefault()')
35}
36
37function stopPropagation(event) {
38  event.stopPropagation()
39  systemMessages.value.unshift({ id: nextMessageId++, text: 'Clicked button - propagation stopped' })
40}
41
42function handleContainerClick() {
43  systemMessages.value.unshift({ id: nextMessageId++, text: 'Container clicked' })
44}
45
46function runOnce() {
47  systemMessages.value.unshift({ id: nextMessageId++, text: 'This handler runs only once!' })
48}
49
50function handleEnter() {
51  systemMessages.value.unshift({ id: nextMessageId++, text: `Search executed: ${searchQuery.value}` })
52  searchQuery.value = ''
53}
54
55function handleEscape() {
56  searchQuery.value = ''
57  systemMessages.value.unshift({ id: nextMessageId++, text: 'Search cleared with ESC' })
58}
59
60function handleCtrlS(event) {
61  event.preventDefault()
62  systemMessages.value.unshift({ id: nextMessageId++, text: 'Mission saved (Ctrl+S)' })
63}
64
65function submitMission(event) {
66  event.preventDefault()
67  logs.value.unshift({
68    id: Date.now(),
69    command: `Mission: ${formData.value.mission} | Priority: ${formData.value.priority}`,
70    timestamp: new Date().toLocaleTimeString()
71  })
72  formData.value.mission = ''
73  formData.value.description = ''
74}
75</script>
76
77<template>
78  <div class="modifiers-lab">
79    <h1>Modifiers Laboratory - NOVA LAB</h1>
80
81    <!-- Event Modifiers Section -->
82    <section class="lab-section">
83      <h2>Event Modifiers</h2>
84
85      <!-- .prevent modifier -->
86      <div class="demo-card">
87        <h3>.prevent - Preventing default action</h3>
88        <form @submit.prevent="preventRefresh" class="demo-form">
89          <input
90            type="text"
91            placeholder="This form will not refresh the page"
92            class="input-field"
93          >
94          <button type="submit" class="btn btn-primary">
95            Submit (preventDefault)
96          </button>
97        </form>
98        <code class="code-hint">@submit.prevent="preventRefresh"</code>
99      </div>
100
101      <!-- .stop modifier -->
102      <div class="demo-card">
103        <h3>.stop - Stopping event propagation</h3>
104        <div @click="handleContainerClick" class="click-container">
105          Click the container (event propagates)
106          <button @click.stop="stopPropagation" class="btn btn-warning">
107            Click the button (stop propagation)
108          </button>
109        </div>
110        <code class="code-hint">@click.stop="stopPropagation"</code>
111      </div>
112
113      <!-- .once modifier -->
114      <div class="demo-card">
115        <h3>.once - Handler runs only once</h3>
116        <button @click.once="runOnce" class="btn btn-success">
117          Click me (works only once)
118        </button>
119        <code class="code-hint">@click.once="runOnce"</code>
120      </div>
121
122      <!-- Multiple modifiers -->
123      <div class="demo-card">
124        <h3>Combining modifiers</h3>
125        <form @submit.prevent.stop="handleCommand" class="demo-form">
126          <input
127            v-model="commandInput"
128            type="text"
129            placeholder="Enter command..."
130            class="input-field"
131          >
132          <button type="submit" class="btn btn-primary">
133            Execute Command
134          </button>
135        </form>
136        <code class="code-hint">@submit.prevent.stop="handleCommand"</code>
137      </div>
138    </section>
139
140    <!-- Key Modifiers Section -->
141    <section class="lab-section">
142      <h2>Key Modifiers</h2>
143
144      <!-- .enter modifier -->
145      <div class="demo-card">
146        <h3>.enter - Detects the Enter key</h3>
147        <input
148          v-model="searchQuery"
149          @keyup.enter="handleEnter"
150          type="text"
151          placeholder="Type and press Enter..."
152          class="input-field"
153        >
154        <code class="code-hint">@keyup.enter="handleEnter"</code>
155      </div>
156
157      <!-- .esc modifier -->
158      <div class="demo-card">
159        <h3>.esc - Detects the Escape key</h3>
160        <input
161          v-model="searchQuery"
162          @keyup.esc="handleEscape"
163          type="text"
164          placeholder="Type and press ESC to clear..."
165          class="input-field"
166        >
167        <code class="code-hint">@keyup.esc="handleEscape"</code>
168      </div>
169
170      <!-- System modifier keys -->
171      <div class="demo-card">
172        <h3>.ctrl + key - Key combinations</h3>
173        <textarea
174          v-model="formData.description"
175          @keydown.ctrl.s="handleCtrlS"
176          placeholder="Press Ctrl+S to save..."
177          class="textarea-field"
178          rows="4"
179        ></textarea>
180        <code class="code-hint">@keydown.ctrl.s="handleCtrlS"</code>
181      </div>
182
183      <!-- Multiple key modifiers -->
184      <div class="demo-card">
185        <h3>Complete form with modifiers</h3>
186        <form @submit.prevent="submitMission" class="mission-form">
187          <input
188            v-model="formData.mission"
189            type="text"
190            placeholder="Mission name (Enter to submit)..."
191            class="input-field"
192          >
193          <select v-model="formData.priority" class="select-field">
194            <option value="low">Low Priority</option>
195            <option value="medium">Medium Priority</option>
196            <option value="high">High Priority</option>
197          </select>
198          <button type="submit" class="btn btn-primary">
199            Submit Mission
200          </button>
201        </form>
202      </div>
203    </section>
204
205    <!-- System Messages -->
206    <section class="lab-section">
207      <h2>System Messages</h2>
208      <div class="messages-container">
209        <div
210          v-for="msg in systemMessages.slice(0, 10)"
211          :key="msg.id"
212          class="message-item"
213        >
214          <span class="message-time">{{ new Date().toLocaleTimeString() }}</span>
215          <span class="message-text">{{ msg.text }}</span>
216        </div>
217      </div>
218    </section>
219
220    <!-- Command Logs -->
221    <section class="lab-section" v-if="logs.length > 0">
222      <h2>Command Logs</h2>
223      <div class="logs-container">
224        <div
225          v-for="log in logs"
226          :key="log.id"
227          class="log-item"
228        >
229          <span class="log-time">{{ log.timestamp }}</span>
230          <span class="log-command">{{ log.command }}</span>
231        </div>
232      </div>
233    </section>
234
235    <!-- Info Box -->
236    <div class="info-box">
237      <h3>Event & Key Modifiers in Vue</h3>
238      <div class="info-grid">
239        <div>
240          <h4>Event Modifiers:</h4>
241          <ul>
242            <li><strong>.prevent</strong> - event.preventDefault()</li>
243            <li><strong>.stop</strong> - event.stopPropagation()</li>
244            <li><strong>.once</strong> - handler only once</li>
245            <li><strong>.capture</strong> - capture mode</li>
246            <li><strong>.self</strong> - only if target === currentTarget</li>
247            <li><strong>.passive</strong> - { passive: true }</li>
248          </ul>
249        </div>
250        <div>
251          <h4>Key Modifiers:</h4>
252          <ul>
253            <li><strong>.enter</strong> - Enter key</li>
254            <li><strong>.tab</strong> - Tab key</li>
255            <li><strong>.delete</strong> - Delete/Backspace</li>
256            <li><strong>.esc</strong> - Escape key</li>
257            <li><strong>.space</strong> - Space key</li>
258            <li><strong>.up/.down/.left/.right</strong> - Arrow keys</li>
259            <li><strong>.ctrl/.alt/.shift/.meta</strong> - System keys</li>
260          </ul>
261        </div>
262      </div>
263    </div>
264  </div>
265</template>
266
267<style scoped>
268.modifiers-lab {
269  background: linear-gradient(135deg, #0a0e27 0%, #1a1e3f 100%);
270  min-height: 100vh;
271  padding: 2rem;
272  color: #ffffff;
273  font-family: 'Courier New', monospace;
274}
275
276h1 {
277  text-align: center;
278  color: #00ff88;
279  text-shadow: 0 0 20px rgba(0, 255, 136, 0.5);
280  margin-bottom: 3rem;
281}
282
283h2 {
284  color: #00b4d8;
285  margin-bottom: 1.5rem;
286  border-bottom: 2px solid #00b4d8;
287  padding-bottom: 0.5rem;
288}
289
290h3 {
291  color: #00ff88;
292  font-size: 1.1rem;
293  margin-bottom: 1rem;
294}
295
296.lab-section {
297  background: rgba(0, 255, 136, 0.05);
298  border: 2px solid #00ff88;
299  border-radius: 10px;
300  padding: 2rem;
301  margin-bottom: 2rem;
302}
303
304.demo-card {
305  background: rgba(0, 180, 216, 0.1);
306  border: 2px solid #00b4d8;
307  border-radius: 8px;
308  padding: 1.5rem;
309  margin-bottom: 1.5rem;
310}
311
312.demo-form {
313  display: flex;
314  gap: 1rem;
315  margin-bottom: 1rem;
316  flex-wrap: wrap;
317}
318
319.mission-form {
320  display: flex;
321  flex-direction: column;
322  gap: 1rem;
323}
324
325.input-field, .select-field, .textarea-field {
326  background: rgba(0, 0, 0, 0.3);
327  border: 2px solid #00b4d8;
328  border-radius: 5px;
329  padding: 0.75rem;
330  color: #ffffff;
331  font-family: 'Courier New', monospace;
332  font-size: 1rem;
333  flex: 1;
334  min-width: 200px;
335}
336
337.input-field:focus, .select-field:focus, .textarea-field:focus {
338  outline: none;
339  border-color: #00ff88;
340  box-shadow: 0 0 10px rgba(0, 255, 136, 0.3);
341}
342
343.input-field::placeholder, .textarea-field::placeholder {
344  color: rgba(255, 255, 255, 0.5);
345}
346
347.btn {
348  padding: 0.75rem 1.5rem;
349  border: none;
350  border-radius: 5px;
351  font-size: 1rem;
352  font-weight: bold;
353  cursor: pointer;
354  transition: all 0.3s ease;
355  font-family: 'Courier New', monospace;
356}
357
358.btn-primary {
359  background: #00ff88;
360  color: #0a0e27;
361}
362
363.btn-warning {
364  background: #ffb400;
365  color: #0a0e27;
366}
367
368.btn-success {
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.click-container {
379  background: rgba(255, 180, 0, 0.1);
380  border: 2px dashed #ffb400;
381  border-radius: 5px;
382  padding: 2rem;
383  text-align: center;
384  cursor: pointer;
385  transition: all 0.3s ease;
386  margin-bottom: 1rem;
387}
388
389.click-container:hover {
390  background: rgba(255, 180, 0, 0.2);
391}
392
393.code-hint {
394  display: block;
395  background: rgba(0, 0, 0, 0.5);
396  border-left: 4px solid #00ff88;
397  padding: 0.75rem;
398  border-radius: 5px;
399  color: #00ff88;
400  font-family: 'Courier New', monospace;
401  font-size: 0.9rem;
402}
403
404.messages-container, .logs-container {
405  background: rgba(0, 0, 0, 0.3);
406  border-radius: 8px;
407  padding: 1rem;
408  max-height: 300px;
409  overflow-y: auto;
410}
411
412.message-item, .log-item {
413  padding: 0.75rem;
414  background: rgba(0, 180, 216, 0.1);
415  border-left: 4px solid #00b4d8;
416  border-radius: 5px;
417  margin-bottom: 0.5rem;
418  display: flex;
419  gap: 1rem;
420  align-items: center;
421}
422
423.message-time, .log-time {
424  color: #ffb400;
425  font-size: 0.9rem;
426  min-width: 100px;
427}
428
429.message-text, .log-command {
430  color: #ffffff;
431  flex: 1;
432}
433
434.info-box {
435  background: rgba(0, 255, 136, 0.05);
436  border: 2px solid #00ff88;
437  border-radius: 10px;
438  padding: 2rem;
439}
440
441.info-box h3 {
442  margin-top: 0;
443  margin-bottom: 1.5rem;
444}
445
446.info-grid {
447  display: grid;
448  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
449  gap: 2rem;
450}
451
452.info-box h4 {
453  color: #00b4d8;
454  margin-bottom: 1rem;
455}
456
457.info-box ul {
458  list-style: none;
459  padding: 0;
460}
461
462.info-box li {
463  padding: 0.5rem 0;
464  color: #ffffff;
465  border-left: 3px solid #00b4d8;
466  padding-left: 1rem;
467  margin: 0.5rem 0;
468}
469
470.info-box strong {
471  color: #00ff88;
472}
473</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 v-bind directive used for?

  2. 2. What is the shorthand for v-bind:href?

  3. 3. What is v-on used for?

  4. 4. What does <img :src> without a value mean when a src variable exists in <script setup>?

  5. 5. What does .prevent do in @submit.prevent?

  6. 6. What does the .stop modifier do in v-on?

Hands-on tasks in the game

  • Code editor

    Use v-bind (shorthand :) to assign dynamic src, alt, href, and disabled attributes.

  • Click in order

    Arrange the click handling syntax with an argument:

  • Code editor

    Use v-on (or its @ shorthand) to handle the click, submit and keyup events.

  • Horizontal ordering

    Arrange the correct v-bind syntax for a dynamic src attribute:

  • Horizontal ordering

    Arrange the correct v-on syntax for handling a click:

  • Vertical ordering

    Order the steps of handling a click event in Vue:

  • Code editor

    Use the .prevent, .stop, and .once modifiers in event handling.

Useful articles