Vue templates look like plain HTML, but they are compiled into highly optimized render functions. Combined with Vue's reactivity system, this means you describe what the UI should look like for a given state, and Vue figures out the minimal DOM updates when that state changes. In this lesson you will learn interpolation, expressions, attribute binding, and how reactivity actually works under the hood.
The most basic form of data binding is the "mustache" syntax:
<script setup>
import { ref } from 'vue'
const name = ref('Ada')
</script>
<template>
<p>Hello, {{ name }}!</p>
</template>Whenever name.value changes, the paragraph updates automatically. Inside the template you do not need .value -- Vue unwraps refs for you.
Mustaches accept any single JavaScript expression, not just variable names:
<p>{{ name.toUpperCase() }}</p>
<p>{{ items.length > 0 ? 'In stock' : 'Sold out' }}</p>
<p>{{ price * quantity }}</p>Statements such as if or variable declarations are not allowed -- keep templates declarative and move complex logic into computed properties (covered in lesson 5).
Mustaches only work inside text content. To bind an attribute, use v-bind: or its shorthand ::
<img v-bind:src="imageUrl" :alt="imageAlt">
<button :disabled="isLoading">Save</button>
<div :class="{ active: isActive, 'text-danger': hasError }"></div>The object syntax for :class is especially handy: each key becomes a class when its value is truthy. :style accepts an object too: :style="{ color: activeColor, fontSize: size + 'px' }".
Vue 3's reactivity is built on JavaScript Proxies. When you create reactive state, Vue wraps it so it can track which parts of your code read the data and trigger updates when the data is written.
import { ref, reactive } from 'vue'
const count = ref(0) // for primitives (and anything else)
const user = reactive({ // for objects
name: 'Ada',
role: 'admin'
})
count.value++ // triggers any template using count
user.name = 'Grace' // triggers any template using user.nameTwo rules to remember:
ref() wraps a value in an object with a .value property. You need .value in JavaScript, but not in templates.reactive() returns a deep reactive proxy of an object. Do not destructure it (const { name } = user) or you lose reactivity -- the extracted variable is just a plain string.Mustaches escape HTML for safety. If you trust the content, v-html renders it as real markup:
<div v-html="articleBody"></div>Only use v-html with content you control. Rendering user-supplied HTML opens the door to XSS attacks.
:id="'item-' + item.id" -- expressions work inside bindings too.:[attrName]="value" -- dynamic attribute names, where attrName is itself reactive data.disabled are added or removed based on truthiness.<script setup>
import { ref, reactive } from 'vue'
const product = reactive({ name: 'Vue Mug', price: 14.5, stock: 3 })
const discount = ref(0.1)
</script>
<template>
<h2>{{ product.name }}</h2>
<p>Price: ${{ (product.price * (1 - discount)).toFixed(2) }}</p>
<p :class="{ low: product.stock < 5 }">
{{ product.stock > 0 ? product.stock + ' left' : 'Out of stock' }}
</p>
<button :disabled="product.stock === 0" @click="product.stock--">
Buy one
</button>
</template>Click the button and both the stock count and the disabled state stay in sync -- no manual DOM code required.
{{ expression }} interpolates text; any single JavaScript expression is allowed.:attribute (shorthand for v-bind:) to bind attributes, classes, and styles.ref() is for any value and needs .value in scripts; reactive() proxies objects deeply.v-html with untrusted content.Next up: Directives: v-if, v-for, v-bind, v-model -- the core directives that control rendering, lists, and forms.