Template Syntax
A Vue template is HTML with a few extra features that connect it to your data. Everything you write inside <div id="app"> (or in a <template> block of a .vue file) is a template. Vue compiles it into JavaScript that renders the page and updates it when data changes.
In this lesson you will learn the three building blocks of every template: text interpolation, attribute binding and directives. You will also learn which JavaScript is allowed inside a template and how to show HTML safely.
Text interpolation
Double curly braces, often called mustache syntax, print a value as text:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Interpolation</title>
<script src="https://cdn.jsdelivr.net/npm/vue@3.5.44/dist/vue.global.prod.js"></script>
</head>
<body>
<div id="app">
<h2>{{ student.name }} from {{ student.city }}</h2>
<p>Marks: {{ marks }} out of 500</p>
<p>Percentage: {{ (marks / 500 * 100).toFixed(1) }}%</p>
<p>Result: {{ marks >= 200 ? "Pass" : "Try again" }}</p>
<p>Name in capitals: {{ student.name.toUpperCase() }}</p>
<p>Subjects: {{ subjects.join(", ") }}</p>
</div>
<script>
const { createApp, ref } = Vue;
createApp({
setup() {
const student = ref({ name: "Ananya", city: "Jaipur" });
const marks = ref(436);
const subjects = ref(["Maths", "Physics", "Chemistry"]);
return { student, marks, subjects };
}
}).mount("#app");
</script>
</body>
</html>Inside the braces you can write any single JavaScript expression: a piece of code that produces a value. Maths, the ternary operator ? :, method calls such as toUpperCase() and array methods all work.
What you cannot write is a statement, such as a variable declaration or an if block:
<!-- These do NOT work: they are statements, not expressions -->
{{ let total = 5 }}
{{ if (marks > 200) { return "Pass" } }}
<!-- Use an expression instead -->
{{ marks > 200 ? "Pass" : "Fail" }}Keep template expressions short. When logic grows, move it into a computed property, which you will learn in Computed Properties.
Templates can also use a small list of safe global objects such as Math, Date, Number and JSON. Variables you attach to window yourself are not visible inside a template; return them from setup() instead.
Interpolation is always text
Mustaches print values as plain text, never as HTML. If a value contains <b>, the user sees the angle brackets. This is a safety feature: if a user types <script> into a comment form, it cannot run in other people's browsers.
When you really need to render HTML that you trust, use the v-html directive:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>v-html</title>
<script src="https://cdn.jsdelivr.net/npm/vue@3.5.44/dist/vue.global.prod.js"></script>
</head>
<body>
<div id="app">
<p>As text: {{ notice }}</p>
<p>As HTML: <span v-html="notice"></span></p>
</div>
<script>
const { createApp, ref } = Vue;
createApp({
setup() {
const notice = ref("Exam on <strong>Monday</strong>");
return { notice };
}
}).mount("#app");
</script>
</body>
</html>Binding attributes with v-bind
Mustaches do not work inside HTML attributes. Writing src="{{ photo }}" sends the literal text to the browser. To bind an attribute to data, use the v-bind directive or its shorthand, a single colon:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>v-bind</title>
<script src="https://cdn.jsdelivr.net/npm/vue@3.5.44/dist/vue.global.prod.js"></script>
<style>
.card { max-width: 300px; border: 1px solid #ccc; padding: 12px; border-radius: 8px; }
img { width: 100%; }
</style>
</head>
<body>
<div id="app" class="card">
<img v-bind:src="photo" :alt="title">
<h3 :title="title">{{ title }}</h3>
<p>₹{{ price }}</p>
<a :href="link" target="_blank">View details</a>
<p><button :disabled="stock === 0">Add to cart</button> ({{ stock }} left)</p>
<button @click="stock = stock > 0 ? stock - 1 : 0">Someone bought one</button>
</div>
<script>
const { createApp, ref } = Vue;
createApp({
setup() {
const title = ref("Kerala backwater tour");
const photo = ref("https://course.ethicscomputer.in/assets/samples/landscape-400.svg");
const price = ref(1299);
const link = ref("https://example.com/tours/kochi");
const stock = ref(2);
return { title, photo, price, link, stock };
}
}).mount("#app");
</script>
</body>
</html>Click "Someone bought one" twice. When stock reaches 0, the expression stock === 0 becomes true and the "Add to cart" button is disabled.
Some rules for bound attributes:
:src="photo"andv-bind:src="photo"are exactly the same. Everyone uses the short form.- For boolean attributes such as
disabledorchecked, Vue adds the attribute when the value is truthy and removes it when the value isfalse,nullorundefined. - If a bound value is
nullorundefined, the attribute is removed completely. - Since Vue 3.4, when the attribute and the variable have the same name you can drop the value:
:titlemeans:title="title".
You can also bind many attributes at once by passing an object to v-bind with no argument:
<script setup>
const inputAttrs = { id: "pin", type: "text", maxlength: 6, placeholder: "Pincode" };
</script>
<template>
<input v-bind="inputAttrs">
</template>Directives, arguments and modifiers
Attributes that start with v- are directives: special instructions to Vue. You have already used v-bind, v-html, v-model and v-on (written @). A directive can have up to four parts:
<form @submit.prevent="save">...</form>
<!-- name: v-on (written @)
argument: submit (which event)
modifier: .prevent (call event.preventDefault())
value: save (the expression to run) -->
<a :[attrName]="url">Link</a>
<!-- dynamic argument: the attribute name itself comes from data -->- The argument after the colon says what the directive acts on, such as
hrefin:href. - A dynamic argument in square brackets is evaluated as JavaScript. If
attrNameis"href", the result is:href="url". Keep dynamic arguments simple, without spaces or quotes. - Modifiers follow a dot and change the behaviour, such as
.preventor.once.
| Directive | Shorthand | Lesson |
|---|---|---|
v-bind | : | this lesson and Class & Style Bindings |
v-on | @ | Event Handling |
v-if, v-else, v-show | none | Conditional Rendering |
v-for | none | List Rendering |
v-model | none | Forms & v-model |
v-slot | # | Slots |
Two rarely needed directives are worth knowing: v-once renders an element only once and then ignores later data changes, and v-pre makes Vue skip an element so its mustaches appear as plain text.
Common mistakes
- Mustaches in attributes.
href="{{ link }}"does not work. Write:href="link". - Forgetting the colon.
disabled="stock === 0"sets the literal text "stock === 0", which always disables the button. With:disabledthe expression is evaluated. - Statements in templates. Declarations and
ifblocks are not allowed; use a ternary or a computed property. v-htmlwith user content. This is a security hole. Display user text with mustaches.
Key points
{{ expression }}prints the value of a single JavaScript expression as plain text.v-htmlrenders trusted HTML; never use it for user input.v-bind:attror:attrbinds an attribute to data;nullorundefinedremoves it.:titlealone is shorthand for:title="title"(Vue 3.4 and newer).- Directives start with
v-and can have an argument, a dynamic argument and modifiers.
Exercise
Build a "book card" for a shop in Lucknow. Store a book with title, author, price (₹499) and inStock (true). Show the title, author and price; show the cover image {{samples}}/portrait.svg with the title as its alt; and add a "Buy now" button that is disabled when inStock is false. Add a second button that toggles inStock.
Show solution
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Book Card</title>
<script src="https://cdn.jsdelivr.net/npm/vue@3.5.44/dist/vue.global.prod.js"></script>
</head>
<body>
<div id="app">
<img :src="cover" :alt="book.title" width="150">
<h3>{{ book.title }}</h3>
<p>by {{ book.author }}, ₹{{ book.price }}</p>
<button :disabled="!book.inStock">Buy now</button>
<button @click="book.inStock = !book.inStock">Toggle stock</button>
</div>
<script>
const { createApp, ref } = Vue;
createApp({
setup() {
const book = ref({ title: "Gitanjali", author: "Rabindranath Tagore", price: 499, inStock: true });
const cover = ref("https://course.ethicscomputer.in/assets/samples/portrait.svg");
return { book, cover };
}
}).mount("#app");
</script>
</body>
</html>:disabled="!book.inStock" is re-evaluated whenever inStock changes, so the button switches between enabled and disabled.
Quick Quiz
1. How do you bind an image's src to a variable called photo?
Mustaches do not work in attributes; use v-bind:src or its shorthand :src.
2. Which of these is allowed inside {{ }}?
Templates accept a single expression, and a ternary is an expression.
3. Why is v-html dangerous with user input?
Mustaches escape HTML; v-html does not, so only use it for trusted content.
4. In @submit.prevent="save", what is .prevent?
submit is the argument and .prevent is a modifier that calls event.preventDefault().
Learn Vue.js with a mentor
Free lessons get you started. Our Full Stack Web Development training adds real projects and code reviews from working developers.
- Project-based learning
- Mentor code reviews
- Free counselling call
