Vue.js course Β· Module 2: Template Syntax
Class and Style Binding
In this lesson4
Just as NOVA LAB systems adapt holograms to the situation, Vue allows you to dynamically manage interface styles.
The status panel should glow green when the system works and pulse red during a failure. With plain v-bind you would have to glue class names together in a string, for example 'panel ' + (isOnline ? 'active' : ''), and with three conditions that becomes unreadable. That is why :class and :style have special powers in Vue: they accept not only text, but also objects and arrays.
Class Binding - Object Syntax
In the object syntax the key is the class name and the value is a condition: when it is truthy, the class goes onto the element. Names with a hyphen, like 'status-critical', go in quotes. :class can sit next to a regular class attribute - the static classes stay and the dynamic ones are added.
1<template>
2 <div class="status-panel" style="background: #0a0e27;">
3 <div :class="{ active: isOnline, 'status-critical': hasCriticalError }">
4 System Status Display
5 </div>
6
7 <!-- With computed -->
8 <div :class="statusClasses">Computed Status</div>
9 </div>
10</template>With isOnline set to true and no failure, the first inner div gets only the active class, and the outer one keeps its status-panel. The second div takes a ready-made class object from the statusClasses variable, which is a computed property.
A computed property is a derived value: you provide a function, and Vue remembers its result and recalculates it only when the refs used inside change. You read it like a ref, and you will learn the details in module 4. Here it returns a class object:
1<script setup>
2import { ref, computed } from 'vue'
3
4const isOnline = ref(true)
5const hasCriticalError = ref(false)
6
7const statusClasses = computed(() => ({
8 active: isOnline.value,
9 'status-critical': hasCriticalError.value,
10 'neon-glow': isOnline.value
11}))
12</script>When you set hasCriticalError.value = true, the object recalculates itself and the panel gets the status-critical class. The Vue docs call this pattern common and powerful. My choice: an object right in the template for one or two classes, computed for more conditions. The classes themselves are defined by ordinary CSS:
1<style>
2.active {
3 color: #00ff88;
4 border-left: 3px solid #00ff88;
5}
6.status-critical {
7 color: #ff4444;
8 animation: pulse 1s infinite;
9}
10.neon-glow {
11 box-shadow: 0 0 10px #00ff88;
12}
13@keyframes pulse {
14 0%, 100% { opacity: 1; }
15 50% { opacity: 0.5; }
16}
17</style>Vue decides only which class names end up in the class attribute. The look, the pulse animation and the glow remain the business of CSS, exactly as in the first world.
Class Binding - Array Syntax
An array comes in handy when the class names themselves live in variables. Its elements are expressions, so you can use the conditional operator in them or insert an object from the previous section:
1<template>
2 <div class="reactor-display">
3 <div :class="[activeClass, statusClass]">
4 Array syntax
5 </div>
6
7 <!-- With condition -->
8 <div :class="[isOnline ? 'online' : 'offline', statusClass]">
9 Conditional class
10 </div>
11
12 <!-- Mixed -->
13 <div :class="[{ active: isOnline }, statusClass]">
14 Mixed
15 </div>
16 </div>
17</template>
18
19<script setup>
20import { ref } from 'vue'
21
22const activeClass = ref('reactor-active')
23const statusClass = ref('status-nominal')
24const isOnline = ref(true)
25</script>The first div gets the classes reactor-active status-nominal, the second online status-nominal, and the third active status-nominal. The array holds the variables' values, not their names: activeClass means the string reactor-active. When a ternary with an empty string shows up in the array, choose the mixed version with an object - it is shorter and easier to read.
Inline Style Binding
:style accepts an object of CSS properties. camelCase keys such as fontSize are recommended, although hyphenated keys in quotes work too. Numbers need a unit, which is why we append 'px'. You can also pass a ready-made style object or an array of objects:
1<template>
2 <div class="hologram-container">
3 <!-- Object syntax -->
4 <div :style="{ color: neonColor, fontSize: fontSize + 'px' }">
5 Inline styles
6 </div>
7
8 <!-- With object -->
9 <div :style="panelStyle">Style object</div>
10
11 <!-- Array syntax - multiple objects -->
12 <div :style="[baseStyles, highlightStyles]">
13 Multiple style objects
14 </div>
15 </div>
16</template>The first div gets color and font-size: 18px. In an array the objects merge into one, and when a property repeats, the later one wins. The data comes from the script:
1<script setup>
2import { ref, reactive } from 'vue'
3
4const neonColor = ref('#00ff88')
5const fontSize = ref(18)
6
7const panelStyle = reactive({
8 color: '#00b4d8',
9 backgroundColor: '#0a0e27',
10 padding: '1rem',
11 borderRadius: '8px',
12 border: '1px solid #00ff88'
13})
14
15const baseStyles = { color: '#00b4d8' }
16const highlightStyles = { fontSize: '20px', textShadow: '0 0 10px #00b4d8' }
17</script>Changing fontSize.value = 24 enlarges only the text of the first div. baseStyles and highlightStyles are plain, non-reactive objects, so changing them later will not refresh the view - as constants, though, they work perfectly well.
When a style depends on several conditions, prepare the object in a computed property, just like with classes:
1<script setup>
2import { ref, computed } from 'vue'
3
4const oxygenLevel = ref(18)
5
6const alertStyle = computed(() => ({
7 color: oxygenLevel.value < 20 ? '#ff4444' : '#00ff88',
8 fontWeight: oxygenLevel.value < 20 ? 'bold' : 'normal'
9}))
10</script>
11
12<template>
13 <p :style="alertStyle">Oxygen: {{ oxygenLevel }}%</p>
14</template>The template is left with one short binding, and all the logic lives in the script. Even so I recommend classes instead of inline styles wherever the look is known in advance. Keep :style for values that are truly computed from data, like the width of a power bar.
Auto-prefixing
Vue can add browser vendor prefixes, but it does so sparingly: at runtime it checks whether the current browser supports a given property, and only when it does not, it tries the prefixed variants:
1<div :style="{ transform: 'rotateX(45deg)' }">
2 <!-- Vue adds a prefix (e.g. -webkit-) only when the browser does not know the unprefixed version -->
3</div>Modern browsers support transform, so you will see it without a prefix. You can also pass an array of values, for example ['-webkit-box', 'flex'] for display - Vue uses the last supported one. In the next lesson you will decide whether an element should exist at all, thanks to v-if and v-show.
Remember: the object in :class is a switchboard - the key is the class name, and the value decides whether the lamp is on.
Code for this lesson: App.vue
1<script setup>
2import { ref, computed } from 'vue'
3
4const isOnline = ref(true)
5const hasCriticalError = ref(false)
6const modeClass = ref('mode-nominal')
7const neonColor = ref('#00ff88')
8const fontSize = ref(18)
9const oxygenLevel = ref(18)
10
11// style object computed from data: red bold text when oxygen is too low
12const alertStyle = computed(() => ({
13 color: oxygenLevel.value < 20 ? '#ff4444' : '#00ff88',
14 fontWeight: oxygenLevel.value < 20 ? 'bold' : 'normal'
15}))
16</script>
17
18<template>
19 <div class="hologram-lab">
20 <h1>Hologram Lab: classes and styles</h1>
21
22 <div class="controls">
23 <button @click="isOnline = !isOnline">Toggle power</button>
24 <button @click="hasCriticalError = !hasCriticalError">Simulate a failure</button>
25 <button @click="fontSize += 2">Bigger text</button>
26 <button @click="oxygenLevel += 5">Add oxygen</button>
27 </div>
28
29 <!-- Object syntax: the key is the class name, the value is the condition -->
30 <div class="panel" :class="{ active: isOnline, 'status-critical': hasCriticalError }">
31 System status: {{ isOnline ? 'ONLINE' : 'OFFLINE' }}
32 </div>
33
34 <!-- Array syntax: class names from a variable and from a ternary -->
35 <div class="panel" :class="[modeClass, isOnline ? 'online' : 'offline']">
36 Operating mode from a class array
37 </div>
38
39 <!-- Mixed syntax: an object inside the array -->
40 <div class="panel" :class="[{ active: isOnline }, modeClass]">
41 Mixed mode
42 </div>
43
44 <!-- :style with an object: camelCase keys, a number with a px unit -->
45 <p :style="{ color: neonColor, fontSize: fontSize + 'px' }">
46 Text size: {{ fontSize }}px
47 </p>
48
49 <!-- :style with computed: the logic stays in the script -->
50 <p :style="alertStyle">Oxygen: {{ oxygenLevel }}%</p>
51 </div>
52</template>
53
54<style scoped>
55.hologram-lab { background: #0a0e27; color: #e0e0ff; min-height: 100vh; padding: 1.5rem; font-family: 'Courier New', monospace; }
56h1 { color: #00b4d8; font-size: 1.4rem; }
57.controls { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-bottom: 1rem; }
58button { background: transparent; border: 2px solid #00ff88; color: #00ff88; padding: 0.4rem 0.8rem; cursor: pointer; font-family: inherit; }
59.panel { border: 2px solid #334; border-radius: 8px; padding: 0.75rem; margin: 0.5rem 0; }
60.active { color: #00ff88; border-left: 4px solid #00ff88; }
61.status-critical { color: #ff4444; animation: pulse 1s infinite; }
62.online { box-shadow: 0 0 10px #00ff88; }
63.offline { opacity: 0.5; }
64.mode-nominal { border-color: #00b4d8; }
65@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } }
66</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. How do you conditionally add a CSS class in Vue?
2. How do you assign multiple classes dynamically using an array?
3. How do you dynamically set text color in Vue?
Hands-on tasks in the game
- Code editor
Use :class with object syntax and array syntax to conditionally add classes.
- Click in order
Arrange the dynamic inline style syntax:
- Horizontal ordering
Arrange the correct syntax for conditional class binding:
- Vertical ordering
Arrange the class binding methods from simple to mixed:
- Code editor
Use :style to dynamically set colors and font size.