Template Syntax

Vue.js · Lesson 3 of 29 6 min read Updated

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:

HTMLTry it yourself
<!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:

HTML
<!-- 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:

HTMLTry it yourself
<!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:

HTMLTry it yourself
<!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" and v-bind:src="photo" are exactly the same. Everyone uses the short form.
  • For boolean attributes such as disabled or checked, Vue adds the attribute when the value is truthy and removes it when the value is false, null or undefined.
  • If a bound value is null or undefined, the attribute is removed completely.
  • Since Vue 3.4, when the attribute and the variable have the same name you can drop the value: :title means :title="title".

You can also bind many attributes at once by passing an object to v-bind with no argument:

HTML
<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:

HTML
<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 href in :href.
  • A dynamic argument in square brackets is evaluated as JavaScript. If attrName is "href", the result is :href="url". Keep dynamic arguments simple, without spaces or quotes.
  • Modifiers follow a dot and change the behaviour, such as .prevent or .once.
DirectiveShorthandLesson
v-bind:this lesson and Class & Style Bindings
v-on@Event Handling
v-if, v-else, v-shownoneConditional Rendering
v-fornoneList Rendering
v-modelnoneForms & 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 :disabled the expression is evaluated.
  • Statements in templates. Declarations and if blocks are not allowed; use a ternary or a computed property.
  • v-html with 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-html renders trusted HTML; never use it for user input.
  • v-bind:attr or :attr binds an attribute to data; null or undefined removes it.
  • :title alone 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
HTML
<!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?

2. Which of these is allowed inside {{ }}?

3. Why is v-html dangerous with user input?

4. In @submit.prevent="save", what is .prevent?

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