Template Syntax

This section documents the current Vue 3.x release line as published at the official Vue.js documentation, which is the reference these pages are written and verified against. No specific patch version is pinned. The Composition API with <script setup> is the authoring style used throughout; the Options API is shown only as a contrast.

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production, since Vue and its ecosystem iterate quickly.

This section’s bibliography lists the reference material consulted while preparing these pages.

Vue templates are valid HTML extended with interpolations and directives. The compiler turns a template into a render function; at runtime Vue keeps the DOM in sync with the component’s reactive state. This page follows Template Syntax and the built-in directives reference, then covers binding class and style.

Text interpolation

The double-brace mustache syntax inserts a value as plain text. It reacts to changes and is always escaped, so the content is never parsed as HTML.

<template>
  <span>Message: {{ msg }}</span>
  <span>{{ ok ? 'YES' : 'NO' }}</span>
</template>

To skip compilation of a block entirely, use v-pre; to show the raw markup until the component is ready, v-cloak paired with a CSS rule. Both are in the directives reference.

Raw HTML with v-html

\{\{ }} cannot output real HTML. v-html sets an element’s innerHTML from a string:

<template>
  <div v-html="renderedMarkdown"></div>
</template>
v-html runs whatever markup you give it. Never pass user-supplied or otherwise untrusted content through it — that is a cross-site scripting hole. Sanitize on the server or with a vetted library first, and see Security and Accessibility for the full rules.

Attribute bindings with v-bind

Mustaches do not work inside attributes. Bind an attribute to an expression with v-bind:, almost always written with its : shorthand:

<template>
  <img :src="imageUrl" :alt="imageAlt">
  <a :href="`/users/${user.id}`">Profile</a>
</template>

Boolean attributes. When the bound value is null, undefined or false, Vue removes the attribute entirely; any other value adds it:

<template>
  <button :disabled="isSubmitting">Save</button>
</template>

Same-name shorthand. If the attribute and the bound variable share a name, Vue 3.4+ lets you write just :id instead of :id="id".

Dynamic attribute names. Put an expression in square brackets to choose the attribute at runtime:

<template>
  <button :[attrName]="attrValue">Go</button>
</template>

The expression must resolve to a string (or null to bind nothing); it is lower-cased by the browser, so avoid uppercase and whitespace in it.

Binding an object of attributes. v-bind with no argument spreads every own property of an object onto the element — useful for forwarding a bag of props or DOM attributes:

<script setup>
const attrs = { id: 'main', 'data-role': 'panel', tabindex: 0 }
</script>

<template>
  <section v-bind="attrs">...</section>
</template>

Expressions in bindings, and their limits

Each binding holds one JavaScript expression — something that could sit after return. It is evaluated in a sandbox with access to the component’s template scope plus a short allowlist of globals (Math, Date, JSON, and a few more).

<template>
  <p>{{ count + 1 }}</p>
  <p>{{ message.split('').reverse().join('') }}</p>
  <p :class="isActive ? 'on' : 'off'"></p>
</template>

What is not allowed: statements (if, for, variable declarations), flow control (use the ternary operator or a computed property instead), assignment as the whole expression, and arbitrary user globals such as window or fetch. When logic grows past a readable one-liner, move it into an computed property or a method.

Anatomy of a directive

A directive is a template attribute whose name starts with v-. Its value is a single expression, except for a few (v-else, v-pre) that take none. The full grammar:

v-on : click .prevent .once = "handler"
 │      │       │              │
name   argument modifiers      value
  • Name — v-on, v-bind, v-if, v-for, v-model, …​ the directive to apply.

  • Argument — after the colon, names the target: the event for v-on:click, the attribute for v-bind:href, the slot for v-slot:header.

  • Dynamic argument — wrap the argument in square brackets to compute it: v-on:[eventName], v-bind:[attrName].

  • Modifiers — dot-prefixed flags that adjust behaviour: .prevent calls event.preventDefault(), .stop stops propagation, .once detaches after one call, .number / .trim / .lazy shape v-model.

Shorthands

Three directives are common enough to have a one-character form:

Directive Shorthand Example

v-bind:

:

:href="url"  — also :[key]="value"

v-on:

@

@click="onClick" — also @[event]="handler"

v-slot:

#

#header on a <template> inside a component

The shorthands are the idiomatic form; the long v- names appear mostly in documentation.

Binding class

:class accepts more than a string, and its result is merged with any static class on the same element. See Class and Style Bindings.

Object syntax — keys are class names, values decide whether each is present:

<template>
  <div class="card" :class="{ active: isActive, 'is-error': hasError }">...</div>
</template>

Rendered as class="card active" when isActive is truthy and hasError is not. The object can be a reactive object or a computed property returning one.

Array syntax — a list of class names or expressions; nest an object for conditional entries:

<template>
  <div :class="[baseClass, isActive ? 'active' : '', { disabled: isDisabled }]">...</div>
</template>

On a component — a class set by the parent is added to the component’s single root element (it does not replace the classes the component sets on itself). This is part of fallthrough attribute behaviour; with multiple root elements you direct it with :class="$attrs.class".

Binding style

Object syntax — the natural form. Property names may be camelCase or the CSS kebab-case (quoted):

<script setup>
import { ref } from 'vue'
const activeColor = ref('teal')
const fontSize = ref(14)
</script>

<template>
  <p :style="{ color: activeColor, fontSize: fontSize + 'px' }">...</p>
  <p :style="styleObject">bind a whole reactive object</p>
</template>

Auto-prefixing — when a CSS property needs a vendor prefix, Vue adds the right one at runtime after testing the current browser.

Multiple values — give an array for a property and Vue keeps the last value the browser accepts, letting you ship a fallback:

<template>
  <div :style="{ display: ['-webkit-box', '-ms-flexbox', 'flex'] }">...</div>
</template>

Array syntax on :style itself merges several style objects onto one element:

<template>
  <div :style="[baseStyles, overrideStyles]">...</div>
</template>

See also