Class and style bindings
Turn CSS classes on and off from your state with :class, and set inline styles and CSS variables with :style.
What you'll learn
When you finish, you can do these.- 1Turn CSS classes on and off from your state with :class
- 2Set inline styles and CSS variables with :style
- 3Predict where a class on your own component lands
- 4Spot and fix a missing unit or a missing colon
A to-do list shows each task as a note. One note, Water the plants, changes its look as things happen: highlighted while it is the active task, the one you are on now, outlined in red when it has an error, crossed out when it is done. Each look is a CSS class, a name that your stylesheet gives a style to. Without Vue, you add and remove those classes yourself every time something changes, and one missed moment leaves the wrong look.
In Vue you bind each class to your state, the values that change while someone uses the page. You need one idea from the Directives lesson: a colon in front of an attribute, as in :disabled, keeps it in step with your state. The Code tab holds App.vue: the note, a progress bar, and buttons. Each button changes a ref, a box that holds a value and tells Vue when it changes. By the end you can read every binding in it and add a Done look of your own.
Classes from an object
:class turns classes on and off from your state. It takes an object: each key is a class name, and each value decides whether that class is on.
In App.vue, two true-or-false refs hold the note's state: isActive, true at first, and hasError, false at first. The note's paragraph has a plain class and a bound one, shown here without its :style, which the next section explains:
<p class="note" :class="{ active: isActive, error: hasError }">Water the plants.</p>
{ active: isActive } adds the class active while isActive is true. Vue merges the plain class with the bound ones, so this is what reaches the browser:
In the templateclass="note" and :class="{ active: isActive, error: hasError }", with isActive true and hasError false.
In the browserclass="note active". The plain class comes first, then every key whose value is true. error is left out.
The CSS for these classes sits in the <style> block of App.vue. The binding only decides which of these rules apply:
<style scoped>
.note { padding: 12px; border-radius: 8px; background: #f1f4f8; }
.active { background: #d3efe2; }
.error { outline: 2px solid #cc3e38; }
</style>
On the page, the note starts pale green, the active look. Click Toggle active and the green goes. Click Toggle error and a red outline appears.
A class name with a dash needs quotes, as in { 'is-done': done }, because the object is JavaScript. When a variable has the same name as the class, { done: done } can be shortened to { done }.
:class also takes an array, a list in square brackets, for class names held in variables. Picture a ref theme that holds 'dark': :class="[theme, { active: isActive }]" adds dark, plus active while isActive is true. A string in the array is always added; an object entry adds only its true keys.
Inline styles and CSS variables
:style sets inline styles, CSS written on the element itself, from your state. It takes an object of CSS properties in camelCase, each new word with a capital, because they are JavaScript keys: fontSize for font-size.
In App.vue, the ref size holds the text size as a plain number, 16 at first. A length needs its unit, so the note's opening tag adds 'px':
<p class="note" :class="{ active: isActive, error: hasError }" :style="{ fontSize: size + 'px' }">
Under the progress bar, a readout line says "Text size 16px, progress 40%". Click Bigger text, which adds 2 to size: the text grows and the readout says 18px.
Use :style for values worked out while the page runs, like a size the visitor picks. For a fixed look, a class is easier to read and to change later.
A CSS custom property, a variable in CSS whose name starts with two dashes, is set the same way, with its name in quotes because of the dashes. App.vue uses one for its progress bar: a div with the class bar around an empty span, and a ref progress that holds the percentage, 40 at first. The bar's :style sets --fill from it:
<div class="bar" :style="{ '--fill': progress + '%' }"><span></span></div>
In the <style> block, the inner span reads the variable as its width with var(--fill):
<style scoped>
.bar span { display: block; height: 100%; width: var(--fill); background: #42b883; }
</style>
The green fill covers 40 percent of the bar, and each click on More progress adds 20, up to 100. You pass one value, and the stylesheet decides which rules use it.
Classes on your own components
A class can also go on a component of your own, and Vue decides where it lands. Picture the note written as a component: one file, TodoNote.vue, used in App.vue as a tag, <TodoNote />. The Code tab has no such file; the Components and props lesson, later in this track, builds real ones.
A class on that tag, as in <TodoNote class="big" />, is not a prop, one of the named inputs a component declares. Vue adds it to the component's root element, the single element at the top of its template, and merges it with the classes already there: a root <p class="note"> becomes <p class="note big">.
That only works with one root. If the template has two top-level elements side by side, a fragment, Vue cannot pick one. It applies the class to neither and warns: Extraneous non-props attributes (class) were passed to component but could not be automatically inherited because component renders fragment or text or teleport root nodes. Wrap the two in one element, and the class lands on it.
What goes wrong
Two mistakes make a binding do nothing, with no warning. The first is a length without a unit. With this :style, each click on Bigger text moves the readout to 18px, 20px, 22px, and the text never changes size.
<p class="note" :style="{ fontSize: size }">Water the plants.</p>
<p class="note" :style="{ fontSize: size + 'px' }">Water the plants.</p>
Vue does not add units. It hands the browser font-size: 18, the browser rejects a length without a unit, and the rule is dropped. A few properties, such as opacity, take plain numbers; lengths always need px, rem or %.
The second is a missing colon in front of class. Toggle active flips isActive, but the note never turns green, and the button seems to do nothing.
<p class="{ active: isActive }">Water the plants.</p>
<p :class="{ active: isActive }">Water the plants.</p>
Without the colon the attribute is plain text, so the element's class is literally { active: isActive }. The browser reads that as four class names, {, active:, isActive and }, and none of them is active.
What to remember
:class="{ name: condition }"adds each class whose condition is true, and merges with a plainclassattribute. An array adds class names held in variables.:stylekeys are camelCase, and lengths need a unit:size + 'px', neversizealone.- A CSS custom property such as
'--fill'passes one value to the stylesheet. Quote its name. - A class on a component lands on its single root element. With more than one root element, Vue warns and applies it nowhere.
- Without the colon,
class="{ active: isActive }"is plain text, and no class follows your state.
Try it
Open the Code tab and change the starter code. Each task says what you should see.- Add
const done = ref(false), a.done { text-decoration: line-through; }rule in the<style>block, anddoneto the note's class object:{ active: isActive, error: hasError, done }. Add<button @click="done = true">Done</button>and click it: the note is crossed out. - Add
<button :disabled="size <= 12" @click="size -= 2">Smaller</button>. Click it twice: the text shrinks, the readout says 12px, and the Smaller button is disabled. - In the note's
:style, delete+ 'px'. Click Bigger text: the readout climbs but the text keeps its size. Put+ 'px'back and the text grows again.