skip to content

Template Syntax

Vue's HTML-based template language: interpolation, directives for conditionals, lists, bindings, events and forms, plus custom directives and template refs. A fast test of Vue fluency.

part ofVue.jsoverview, primer and where to startread it →
on this pageshow

explore

questions

page 1 of 2

In Vue 3, how do :class object and array syntax work, and how would you build a button's classes from variant, size and disabled?

level: juniorimportance: must knowfreq 74%

answer

  1. strings, objects and arrays
  2. truthiness toggles an object key
  3. arrays may nest objects
  4. static class merges with :class

basics

~20 s

Vue's :class accepts a string, an object whose keys are added when their values are truthy, or an array of either. It merges with a static class attribute, so a computed can combine variant and size names with conditional flags like is-disabled.

solid answer

~40 s

`:class` is `v-bind` with extra value types. An **object** such as `{ 'is-disabled': disabled }` adds each key whose value is truthy. An **array** such as `['btn', `btn--${variant}`]` adds each entry, and entries can themselves be objects or nested arrays; empty strings and falsy object values add nothing. A static `class="btn"` and a `:class` on the same element are merged, not overwritten. For a button, the usual shape is a static base class plus a `computed` that returns an array: the variant and size classes as strings, and an object for boolean states like `is-disabled` and `is-loading`. Vue normalises all of it into one class string and updates the element when the reactive inputs change.

code

vue · 14 lines
vue
<script setup lang="ts">
import { ref } from 'vue'

const isActive = ref(true)
const hasError = ref(false)
const size = ref<'sm' | 'lg'>('sm')
</script>

<template>
  <!-- renders class="tab active" -->
  <div class="tab" :class="{ active: isActive, 'text-danger': hasError }">Tab</div>
  <!-- renders class="btn--sm" -->
  <div :class="['btn--' + size, { 'text-danger': hasError }]">Mixed</div>
</template>

go deeper

for a junior

Recall the three forms — string, object toggled by truthiness, array of either — and that :class merges with a static class.

for a middle

Explain how Vue normalises nested arrays and objects into one string, and why a computed returning an array suits enumerated plus boolean props.

for a senior

Keep class logic in one tested computed, avoid string concatenation bugs like 'btn--undefined', and separate root-class merging from the parent.

for a principal

Choose a class-naming contract for the design system's components so variants and states map to classes predictably across teams.

## Why class gets special treatment Every attribute can be bound with `v-bind` (shorthand `:`), but building a class string by concatenation is error-prone: stray spaces, leftover `undefined`, classes that should disappear but do not. So Vue 3 accepts **three value types** for `:class`: - a **string**, used as-is: `:class="'btn btn--primary'"`; - an **object**, where each key is a class name that is added when its value is **truthy**: `:class="{ 'is-disabled': disabled }"`; - an **array**, where each entry is normalised in turn and may itself be a string, an object or another array: `:class="['btn--' + size, { 'is-loading': loading }]"`. Vue flattens whatever you give it into one space-separated string (a helper called `normalizeClass`) before touching the DOM. ## Static class plus :class A plain `class` attribute and a `:class` binding on the same element **coexist**. The Vue guide's example renders `class="static active"` from `class="static"` and `:class="{ active: isActive }"`. So the idiom is: - put the classes every instance always has in the static attribute; - put everything that depends on state in `:class`. ## The button, built step by step The component takes a `variant` (`primary`, `ghost`, `danger`), a `size` (`sm`, `md`, `lg`), and `disabled` and `loading` flags. ```vue <script setup lang="ts"> import { computed } from 'vue' const props = defineProps<{ variant: 'primary' | 'ghost' | 'danger' size?: 'sm' | 'md' | 'lg' disabled?: boolean loading?: boolean }>() const classes = computed(() => [ `btn--${props.variant}`, `btn--${props.size ?? 'md'}`, { 'is-disabled': props.disabled, 'is-loading': props.loading }, ]) </script> <template> <button class="btn" :class="classes" :disabled="disabled || loading"> <slot /> </button> </template> ``` For `variant="danger" size="sm" loading`, the element renders `class="btn btn--danger btn--sm is-loading"`. When `loading` turns false, Vue recomputes the array and removes `is-loading`; nothing else changes. Why this shape works well: 1. **Enumerated props become strings** in the array — one class per value, no `if` chain. 2. **Boolean props become object keys**, which is exactly what the object syntax is for. 3. **A `computed`** keeps the template short and caches the result until one of the props it read changes. ## Object, array, or both? | Need | Best fit | Example | |---|---|---| | a few on/off classes | object | `{ active: isActive, 'text-danger': hasError }` | | classes named by a value | array of strings | `[activeClass, errorClass]` | | a mix of the two | array containing an object | `[size, { 'is-disabled': disabled }]` | | one conditional class in a list | ternary in an array | `[isActive ? activeClass : '', errorClass]` | The guide notes that ternaries get verbose with several conditions, which is why an object inside an array is usually cleaner. ## Mistakes interviewers listen for - **Quoting keys wrongly.** A class name with a hyphen must be a quoted key: `{ 'text-danger': hasError }`. - **Expecting falsy array entries to vanish only when they are `false`.** Vue skips any entry that normalises to an empty string, including `''`, `null`, `undefined` and `false`, but a string like `'undefined'` from careless concatenation (`'btn--' + undefined`) is added literally. - **Rebuilding the string by hand.** String concatenation throws away the object and array syntax that make classes toggle cleanly. - **Believing `:class` replaces the static attribute.** They merge. How classes set on the **component** by its parent are merged onto this button's root element is a separate topic — fallthrough attributes — and follows its own rules.

  • In Vue 3, does a :class binding replace the element's static class attribute?
    No. Vue merges them: `class="btn"` plus `:class="{ active: isActive }"` renders `btn active` when `isActive` is truthy. Put invariant classes in the static attribute and state-dependent ones in the binding.
  • Why bind :class to a computed rather than write the object inline for a Vue 3 button with several states?
    A `computed` keeps the template readable, gives the class logic a name you can test, and caches the result until one of the reactive values it read changes. The inline form works the same at runtime, but long objects in templates are hard to review.

The object syntax is a panel of labelled light switches: each class name is a label, and the class is on exactly when its switch is truthy. The array syntax is a list of panels and fixed lamps that Vue wires together into one row.

saying these in an interview costs you the question

  • A :class binding overwrites the static class attribute on the same element.
  • Object syntax adds a class only when its value is exactly true.
  • Arrays passed to :class cannot contain objects.
  • 'btn--' + undefined is dropped automatically because it is falsy.
  • You must join class names into one string before binding them.
open as a page

In Vue 3, how do v-if and v-show differ, and which would you use for a dropdown versus a rarely opened admin panel?

level: juniorimportance: must knowfreq 82%

basics

~20 s

Vue's v-if adds and removes its block, destroying child components and listeners, and renders nothing while false; v-show always renders and only toggles inline display. Use v-show for a frequently toggled dropdown, v-if for a rarely shown admin panel.

open as a page

In Vue 3, what is the difference between a method handler and an inline handler in v-on, and how does each receive the native event?

level: juniorimportance: must knowfreq 72%

basics

~20 s

Vue's template compiler treats a v-on value that is a name or property path (save, form.save) as a method handler, called with the native event. Anything else (save('draft'), count++) is an inline handler, where the event is available as $event.

open as a page

In Vue 3, how does v-model on a checkbox behave when bound to a boolean versus an array, and what does the value attribute do?

level: juniorimportance: must knowfreq 68%

basics

~20 s

Bound to a boolean, a checkbox's v-model stores true or false. Bound to an array or Set, each checked box adds its own value attribute to the collection and unchecking removes it, so every box in the group needs a distinct value.

open as a page

In a Vue 3.5 `<script setup>` component, how do you focus an `<input>` on mount with a template ref, and why not during setup?

level: juniorimportance: must knowfreq 68%

basics

~10 s

Put ref="email" on the input, get it with useTemplateRef('email') (Vue 3.5) or a same-named ref(null), and call focus() in onMounted. During setup the element does not exist yet, so the ref is still null.

open as a page

In Vue 3, how do {{ }} interpolation, v-text and v-html differ in what they write, and what does v-html content skip?

level: juniorimportance: must knowfreq 70%

basics

~20 s

Vue's {{ }} inserts escaped text into part of an element, v-text replaces the whole textContent, and v-html replaces innerHTML with raw markup that Vue never compiles, so it holds no bindings or components and is an XSS risk.

open as a page

In Vue 3, what happens when v-if and v-for are placed on the same element, and how did that change from Vue 2?

level: middleimportance: must knowfreq 64%

basics

~20 s

In Vue 3, v-if takes priority over v-for on the same element, so the condition cannot see the loop alias; Vue 2 ran v-for first. Move the loop to a wrapping <template> or the condition to a container.

open as a page

In Vue 3, what does v-for do when a list rendered without :key changes order, and when is that default safe?

level: middleimportance: must knowfreq 78%

basics

~20 s

Without :key, Vue 3 patches each v-for element in place by position instead of moving DOM nodes; that is only safe when rows hold no component state or DOM state such as input values or focus.

open as a page

In a Vue 3 table of sortable rows, each a child component with its own input draft, why do drafts land on the wrong rows after sorting with :key="index"?

level: seniorimportance: must knowfreq 62%

basics

~20 s

With :key="index" Vue reuses the row component at each position after sorting; only its props change, and a draft initialised once in setup stays with the position. Keying by row.id moves each instance with its row.

open as a page

A Vue 3 page must render rich text from a CMS that users can edit; which rendering approaches are unsafe, and what would you ship instead?

level: seniorimportance: must knowfreq 55%

basics

~20 s

In Vue 3, v-html on unsanitized CMS HTML, compiling CMS strings as Vue templates, and mounting Vue over server-rendered user content are all unsafe. Sanitize with an allowlist before v-html, or render structured content through your own components.

open as a page

In a Vue 3 `<script setup>` component, how do you define and use a `v-focus` directive, and which hook should do the focusing?

level: juniorimportance: should knowfreq 55%

basics

~20 s

Declare const vFocus = { mounted: (el) => el.focus() } in script setup and write v-focus on the element. Use mounted because the element is in the document only then; the function shorthand would refocus on every update.

open as a page

In Vue 3, what kinds of values can a v-for directive iterate, and which aliases does each form give the template?

level: juniorimportance: should knowfreq 58%

basics

~10 s

Vue 3's v-for iterates arrays (item, index), plain objects (value, key, index), an integer n (1 to n, plus a 0-based index), strings, and any iterable such as Map or Set.

open as a page

In Vue 3, what does v-bind render for false, null and '' on a boolean attribute like disabled versus on aria-pressed?

level: middleimportance: should knowfreq 42%

basics

~20 s

For boolean attributes such as disabled, Vue 3 includes the attribute for truthy values and '' and omits it for other falsy values. For other attributes, false renders as the string "false"; only null or undefined remove them.

open as a page

In Vue 3, how do v-bind="obj" and the 3.4 same-name shorthand work, and which wins when obj and an attribute share a key?

level: middleimportance: should knowfreq 40%

basics

~20 s

v-bind without an argument spreads an object's keys as attributes or props. In Vue 3 the later declaration wins on a clash, except class and style, which merge. Since 3.4, :id alone is shorthand for :id="id".

open as a page

In Vue 3, what does :style accept, and what does Vue do — and not do — when it applies a style object?

level: middleimportance: should knowfreq 44%

basics

~20 s

Vue's :style takes a CSS string, an object of properties, or an array of objects merged left to right. Vue adds vendor prefixes when needed and clears properties that disappear or become null, but it never appends units such as px to numbers.

open as a page

In Vue 3, when a v-if/v-else pair renders an <input> in each branch, does typed text survive switching branches, and why?

level: middleimportance: should knowfreq 42%

basics

~20 s

No. Vue 3's compiler gives every v-if, v-else-if and v-else branch its own automatic key, so switching branches unmounts one input and mounts a fresh one; Vue 2 reused same-tag elements unless you added keys.

open as a page

In Vue 3, what does a custom directive hook receive for `v-tooltip:top.delay="hint"`, and when is `binding.oldValue` available?

level: middleimportance: should knowfreq 45%

basics

~20 s

Each hook gets el, binding, vnode and, in beforeUpdate and updated, prevVnode. For v-tooltip:top.delay="hint" the binding has value = hint's value, arg = 'top', modifiers = { delay: true }, plus instance and dir; oldValue is set only in beforeUpdate and updated.

open as a page

In Vue 3, how do you bind a Ctrl+Enter submit shortcut on a textarea that Ctrl+Shift+Enter does not trigger, and what does .exact check?

level: middleimportance: should knowfreq 46%

basics

~10 s

Use @keydown.ctrl.enter.exact="submit". .enter matches the key, .ctrl requires Ctrl held, and .exact rejects the event if any other system modifier (Shift, Alt, Meta) is also pressed; without .exact, Ctrl+Shift+Enter would fire too.

open as a page

In Vue 3, why does the order of v-on modifiers matter, as in @click.prevent.self versus @click.self.prevent on a modal backdrop?

level: middleimportance: should knowfreq 52%

basics

~20 s

Vue runs v-on's guard modifiers in the order written. @click.prevent.self calls preventDefault on every click reaching the element, including clicks bubbling from children, then checks .self; @click.self.prevent checks .self first, so only clicks on the element itself are prevented.

open as a page

In Vue 3, why does v-model ignore a value, checked or selected attribute written in the template, and where must the initial value come from?

level: middleimportance: should knowfreq 46%

basics

~20 s

v-model treats the bound JavaScript state as the only source of truth, so on mount it overwrites the element's value, checked or selected state from the model. Declare the initial value in the ref or reactive state, not in the markup.

open as a page

In Vue 3, what do v-model's .lazy, .number and .trim modifiers change on a text input, and why can v-model.number still store a string?

level: middleimportance: should knowfreq 55%

basics

~20 s

On a Vue 3 text input, .lazy syncs on change instead of input, .trim strips surrounding whitespace, and .number casts with parseFloat. When parseFloat fails, such as on an empty field, .number keeps the original string, so the model can hold ''.

open as a page

In Vue 3, how do you make v-model store numbers, objects or custom on/off values from radios, checkboxes and select options?

level: middleimportance: should knowfreq 40%

basics

~20 s

Bind the choice with v-bind: a radio or option with :value stores the raw JavaScript value, and a checkbox's true-value and false-value set what checked and unchecked store. A static value attribute always yields a string.

open as a page

In Vue 3, how should v-for render a filtered or sorted view of a reactive array, and why does sort() in a computed backfire?

level: middleimportance: should knowfreq 66%

basics

~20 s

Vue 3 renders a derived view by iterating a computed that returns a new filtered or sorted array; sort() and reverse() mutate the source in place, so a computed must copy first, as in [...items].sort().

open as a page

In Vue 3, a parent holds a template ref to a `<script setup>` child and calls its `reset()`; why does that fail until the child uses defineExpose?

level: middleimportance: should knowfreq 55%

basics

~20 s

A script setup component is closed by default: a parent's template ref receives a proxy that exposes none of its setup bindings. The child must call defineExpose({ reset }) before any await, and the parent then calls childRef.value.reset() after mount.

open as a page

In Vue 3, what does a template ref placed on an element inside `v-for` hold, and why must you not rely on its order?

level: middleimportance: should knowfreq 42%

basics

~20 s

A ref inside v-for collects an array of all rendered elements or component instances. Vue does not guarantee that the array matches the source list's order, so look items up by an identifier, not by index.

open as a page

In a Vue 3 directive such as v-on:[eventName].once="handler", what are its parts, and what rules apply to a dynamic [argument]?

level: middleimportance: should knowfreq 38%

basics

~10 s

A Vue 3 directive reads name:argument.modifiers="value"; a bracketed argument is a JavaScript expression that must evaluate to a string, or null to remove the binding, and cannot contain spaces or quotes.

open as a page

In Vue 3, why does a template expression such as {{ window.innerWidth }} fail, and what may a template expression contain?

level: middleimportance: should knowfreq 45%

basics

~20 s

Vue 3 template expressions see only the component's scope plus a restricted globals allowlist (Math, Date, JSON and similar); window is not on it, so it resolves to undefined. Each binding must be one expression, not statements.

open as a page

A Vue 3 `v-click-outside` directive leaks document listeners and sometimes calls an outdated handler; how should its hooks be written to fix both?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Register one document listener in mounted and store it keyed by the element, update the stored callback in updated when binding.value changes, and remove the listener in unmounted. Leaks come from adding in updated or never removing; stale calls come from capturing binding.value once.

open as a page

When should a Vue 3 codebase implement a behaviour as a custom directive rather than a component, a composable or a built-in binding?

level: seniorimportance: should knowfreq 38%

basics

~20 s

Use a custom directive only when the behaviour needs direct DOM manipulation on a plain element, such as focusing or detecting outside clicks. Use a component when it renders markup, a composable for stateful logic, and built-in bindings whenever they can express it.

open as a page

A Vue 3 modal closes through @click.self on its backdrop, but it also closes when a user drag-selects text in the dialog and releases over the backdrop — why, and how do you fix it?

level: seniorimportance: should knowfreq 34%

basics

~20 s

When a press starts in the dialog and ends on the backdrop, browsers dispatch click to the nearest common ancestor — the backdrop — so .self passes. Record whether mousedown began on the backdrop and close only if it did.

open as a page

showing 1–30 of 38