Vue.js: Template Syntax
Last updated: 2026-10-20
Vue's template syntax is a declarative extension of HTML — it adds "directives" and "interpolation" on top of plain HTML so you can render data into the DOM. The whole syntax is about 90% identical to HTML, so anyone who knows HTML can get productive in 30 minutes.
Vue templates revolve around three actions: interpolation (display data) + directives (bind attributes/events) + expressions (run JS). This lesson covers the standard way to write all three.
1. What You'll Learn
- The 4 ways to use Mustache interpolation
{{ }} - How the three text directives v-text, v-html, and v-once differ
- v-bind attribute binding: the
:shorthand and object syntax - v-on event binding: the
@shorthand and modifiers - What JS expressions are allowed in templates (statements are not)
- What the two edge-case directives v-pre and v-cloak are for
2. A Product Card Rendering Requirement
(1) The Pain: 30 Products, Copy-Paste 30 Times?
Alice is building an e-commerce admin. She needs to display 30 product cards. Her first approach:
<!-- Copied and pasted 30 times — a nightmare... -->
<div class="product-card">
<h3>iPhone 15 Pro</h3>
<p>Price: $999</p>
<button>Edit</button>
</div>
<div class="product-card">
<h3>MacBook Pro</h3>
<p>Price: $2499</p>
<button>Edit</button>
</div>
<!-- ... and 28 more ... -->
Product manager Charlie says, "change the price format to include a currency symbol":
"Alice, you can't hand-edit 30 files. What if there are 5,000 products next month? We need dynamic templates."
(2) The Vue Template Solution: 1 Component, 30 Data Records
5 lines of template × 30 products = zero copy-paste. When a name or price changes, Vue updates the DOM automatically.
(3) The Payoff
With Vue templates:
- Lines of code: 30 × 15 lines = 450 lines → 1 template + 30 data records = 60 lines (-87%)
- Maintenance cost: change one template style → all 30 products update together
- Adding a product: add one line of data → the template renders it automatically
3. Interpolation: Mustache {{ }}
(1) The 4 Interpolation Forms
| Form | Effect | Example |
|---|---|---|
{{ var }} |
Displays a variable's value | <p>{{ username }}</p> → Alice |
{{ expr }} |
Displays the result of a JS expression | <p>{{ count + 1 }}</p> → 6 |
{{ fn() }} |
Calls a function and shows its return value | <p>{{ formatDate(date) }}</p> → 2026-07-02 |
{{ obj.prop }} |
Accesses an object property | <p>{{ user.name }}</p> → Alice |
(2) 5 Key Interpolation Behaviors
(3) Interpolation vs v-text
▶ Example: Multiple Interpolation Forms
▶ Try it YourselfOutput:
Five lines of text — Variable: Alice, Expression: 11, Passed (≥60) (the v-else line stays hidden), Date: 2026-10-18, and User: Alice.
Variable: Alice
Expression: 11
Passed (≥60)
Date: 2026-10-18
User: Alice
(4) 📊 Template vs HTML Cheat Sheet
| Element | HTML | Vue | Notes |
|---|---|---|---|
| Static text | <p>Hello</p> |
<p>{{ msg }}</p> |
Make it dynamic |
| Static attribute | <img src="x.jpg"> |
<img :src="url"> |
Make it dynamic |
| Static class | <div class="box"> |
<div :class="cls"> |
Make it dynamic |
| Static style | <div style="color: red"> |
<div :style="sty"> |
Make it dynamic |
| Click event | <button onclick="fn()"> |
<button @click="fn"> |
Switch to Vue |
| Conditional rendering | None | <div v-if="ok"> |
Vue only |
| List rendering | None | <div v-for="i in list"> |
Vue only |
4. Text Directives: v-text / v-html / v-once
(1) Comparing the Three Directives
| Directive | Purpose | Use case | Safety |
|---|---|---|---|
{{ }} interpolation |
Text interpolation | Default choice | ✅ Safe |
v-text |
Sets the element's textContent | Directive form of interpolation | ✅ Safe |
v-html |
Sets the element's innerHTML | Rendering rich text (use with care) | ⚠️ XSS risk |
v-once |
Renders once, never updates afterwards | Static optimization | ✅ Safe |
(2) The v-html Warning
(3) v-once Performance Optimization
5. Attribute Binding: v-bind and the : Shorthand
(1) v-bind Basics
(2) class and style Binding (Key)
(3) style Binding
▶ Example: 5 v-bind Forms
▶ Try it YourselfOutput:
Five numbered lines. The images load from the placeholder URL and get alt="Logo";
the first span carries class="active", the second class="active error";
the link points to https://example.com/users/1001.
1. Basic: [image]
2. Dynamic attribute: [image]
3. Object syntax: A → class="active"
4. Array syntax: B → class="active error"
5. String concatenation: Profile → https://example.com/users/1001
6. Event Binding: v-on and the @ Shorthand
(1) v-on Basics
(2) Common Event Modifiers (9)
(3) Modifier Chains
▶ Example: 5 v-on Forms
▶ Try it YourselfOutput:
Five controls: clicking 1. Click logs clicked; 2.
+ (count: 0) increments the label to count: 1 on each click; 3.
Delete Item A logs the id and event object; 4. Save logs Save button clicked;
pressing Ctrl+Enter in the input logs Ctrl+Enter submitted.
[ 1. Click ]
[ 2. + (count: 0) ]
[ 3. Delete Item A ]
[ 4. Save (stop propagation + default behavior) ]
[ 5. Ctrl+Enter to submit ]
7. Expression Limits (No Statements)
(1) Template Expressions vs JS Statements
(2) Expressions vs Computed Properties
| Scenario | Use an expression | Use a computed property |
|---|---|---|
| Simple calculation (1-2 operators) | ✅ | ❌ |
| Complex logic (multiple steps, loops) | ❌ | ✅ |
| Needs caching | ❌ | ✅ |
| Needs parameters | ✅ | ❌ (use a method instead) |
8. Edge-Case Directives: v-pre / v-cloak
(1) v-pre: Skip Compilation
When to use: displaying Vue template syntax itself (teaching material, documentation sites).
(2) v-cloak: Hide Before Compilation
When to use: preventing users from seeing {{ message }} flash before compilation.
9. Complete Example: Dynamic Product Card
▶ Example: Basic Product Card
▶ Try it YourselfOutput:
A card showing iPhone 15 Pro, $999, and a Only 5 left badge styled with the low-stock class.
The Add to Cart button is enabled (stock is 5, not 0);
clicking it logs Added iPhone 15 Pro to the console.
iPhone 15 Pro
$999
Only 5 left
[ Add to Cart ]
10. Common Mistakes at a Glance
Sections 1-8 covered interpolation, v-text/v-html, v-bind, v-on and expression limits. Here are the most common mistakes in one table — come back to it whenever a template misbehaves.
(1) 📊 5 Common Mistakes
| Mistake | Symptom | Fix |
|---|---|---|
Forgetting .value |
Template uses {{ count }}, JS uses count.value |
The template unwraps automatically; JS needs .value |
Writing if/for inside {{ }} |
Compile error | Use the v-if / v-for directives |
| v-html XSS | User input with <script> executes |
Sanitize with DOMPurify |
Inline handler without $event |
The event object never arrives | Pass it explicitly: @click="fn($event)" |
| Wrong modifier order | Propagation is not stopped | Put .stop before .prevent (as needed) |
❓ FAQ
{{ }} and v-text?{{ }} can be mixed with static text (<p>Hi, {{ name }}</p>), while v-text must own the whole element (<p v-text="msg"></p>). Use {{ }} — it is more flexible.<script> and attack your site. Unless you trust the data source 100% (for example, Markdown you rendered yourself), use {{ }} interpolation or run the value through DOMPurify.sanitize() first.@click.stop.prevent calls stopPropagation() first, then preventDefault().: (drop v-bind), and v-on's shorthand is @ (recommended). Both have been official since Vue 2, and IDE highlighting and autocomplete support them.:class="{ active: isActive, 'text-danger': hasError }", or array syntax :class="[activeClass, errorClass]". You can mix them: :class="[isActive && 'active', { 'error': hasError }]".@keyup.ctrl.s="save" or @keydown.ctrl.s.prevent="save" (adding .prevent blocks the browser's save dialog). Vue 3 also has the .exact modifier, so @click.ctrl.exact requires Ctrl and nothing else.📖 Summary
- Vue template syntax is a declarative HTML extension built on interpolation + directives + expressions
- Mustache interpolation
{{ }}supports variables, expressions, function calls, and ternaries - Template expressions cannot contain if/for/variable declarations (use v-if / v-for)
- v-text replaces the whole element's text; v-html renders rich text (XSS risk)
- v-once optimizes performance (static content renders once); v-pre skips compilation
- v-bind shrinks to
:, v-on shrinks to@, and there are 3 common class-binding forms - 9 event modifiers: .stop / .prevent / .once / .self / .capture / .passive + keyboard/mouse/system
- Choosing between template expressions and computed properties: simple cases use expressions, complex cases use computed
📝 Exercises
-
Basic (⭐) Write a Vue 3 component that renders a user card with:
- Username (interpolation)
- Avatar (v-bind)
- A "Follow" button (v-on)
- Online/offline status (dynamic class toggling)
Use this data: the user object
{ name: 'Alice', avatar: 'alice.jpg', online: true } -
Intermediate (⭐⭐) Build a simple Todo list:
- An input (no v-model yet — simulate it with v-bind:value + @input)
- An add button (@click + expression)
- List rendering (v-for, covered in the next lesson)
- A delete button on each row (@click + argument passing)
Data:
const todos = ref(['Learn Vue', 'Build app', 'Deploy']) -
Challenge (⭐⭐⭐) Write a complete product detail card component that supports:
- 5 product status badges (on sale / out of stock / pre-order / promotion / delisted)
- A different class color for each of the 5 statuses (batch-bound with object syntax)
- Quantity +/- buttons (@click increment/decrement)
- Disabling the "Add to Cart" button when quantity ≥ stock (
:disableddynamic) - At least 3 event modifiers (@click.stop / @submit.prevent and so on)