Vue Templates

Vue templates describe the DOM you want using HTML enriched with mustache interpolation and directives, and Vue compiles that description into an efficient render function for you.

What a Template Actually Is

A Vue template is valid, mostly-ordinary HTML with a small extra syntax layered on top: double-curly interpolation for text, and special attributes called directives for everything else. You never write render-function code by hand -- Vue's compiler does that at build time, turning your markup into a fast function that produces virtual DOM nodes. That compilation step is also why templates can catch mistakes, like referencing an undeclared variable, before your app ever runs in a browser.

Text Interpolation with Mustaches

Double curly braces, often called mustaches, let you drop the result of a JavaScript expression into text content. Anything between the braces is evaluated as a single expression -- not a full statement -- so a ternary or a method call works, but an if statement or a variable declaration does not. The result is automatically converted to a string and, critically, escaped, so interpolated text can never inject live HTML tags into the page.

Interpolating Values and Expressions

<script setup>
import { ref, computed } from 'vue'

const firstName = ref('Ada')
const tasksLeft = ref(3)
const summary = computed(() =>
  tasksLeft.value === 0 ? 'All done!' : `${tasksLeft.value} tasks left`
)
</script>

<template>
  <h2>Hello, {{ firstName }}</h2>
  <p>{{ summary }}</p>
  <p>Shouting version: {{ firstName.toUpperCase() }}</p>
</template>

Binding Attributes with v-bind

Mustaches only work inside text content, not inside HTML attributes -- you can't write id='{{ someId }}'. For attributes, use the v-bind directive, almost always written with its colon shorthand: :src, :class, :disabled. Vue evaluates the expression on the right and keeps the attribute in sync as the underlying data changes. class and style get special treatment: you can bind an object or array to :class and Vue toggles individual class names based on truthy values.

Dynamic Attributes and Classes

<script setup>
import { ref, computed } from 'vue'

const avatarUrl = ref('/images/ada.png')
const isOnline = ref(true)
const statusClass = computed(() => ({
  'status-online': isOnline.value,
  'status-offline': !isOnline.value
}))
</script>

<template>
  <img :src='avatarUrl' alt='User avatar' />
  <span :class='statusClass'>{{ isOnline ? 'Online' : 'Offline' }}</span>
  <button :disabled='!isOnline'>Message</button>
</template>

Rendering Raw HTML with v-html

Because mustache interpolation escapes its output, printing '<strong>bold</strong>' would show the literal tags as text rather than making the word bold. When you genuinely need to inject a string of HTML markup -- content from a trusted CMS field, for instance -- use the v-html directive instead. It replaces the element's inner content with the raw, unescaped HTML you provide.

Injecting Trusted Markup

<script setup>
import { ref } from 'vue'

const articleBody = ref('<p>This paragraph includes <em>emphasis</em> and a <a href=\"/more\">link</a>.</p>')
</script>

<template>
  <article v-html='articleBody'></article>
</template>
Note: v-html only interpolates plain markup -- it does not execute inline script tags, but it will happily render event handler attributes, iframes, or forms. Never pass user-submitted text to v-html without running it through a sanitization library first; treating it as safe is one of the most common ways front-end apps end up vulnerable to cross-site scripting.

Directive Quick Reference

SyntaxPurposeEscapes output?
{{ expr }}Insert text contentYes
v-bind:attr or :attrBind an HTML attribute or propNot applicable
v-htmlInsert raw HTML as inner contentNo
v-textInsert text content, replacing all existing childrenYes
Note: Reach for v-html rarely. In most cases where you're tempted to inject a chunk of markup, a small child component with props and slots produces the same result more safely, and it's easier to style and test.

Exercise: Vue Templates

What is the main difference between v-if and v-show?