Vue template syntax is the layer where component state becomes visible DOM. Interpolation prints text, bindings feed attributes and props, and directives control whether a node exists at all.
The tricky part is that the template is still HTML-shaped while Vue is compiling expressions behind it. A value can update text, a class list, a boolean attribute, or a whole repeated block depending on the directive you choose.
This page focuses on the exact boundary between markup and data: what belongs in {{ }}, what belongs in :attribute, and what should move into script because the expression has become too large to live in the template.
Interpolation is for plain text nodes. Vue evaluates the expression inside the braces, converts the result to a string, escapes it, and places that text into the DOM.
If the data changes later, Vue reruns the render function and updates only the text node that depended on that value.
<template>\n <section class="departure-card">\n <h2>{{ train.line }} {{ train.number }}</h2>\n <p>Platform {{ train.platform }} · {{ train.departureTime }}</p>\n <p>{{ train.statusText }}</p>\n </section>\n</template>\n\n<script setup>\nimport { reactive } from 'vue'\n\nconst train = reactive({\n line: 'Harbor Express',\n number: 'A-19',\n platform: 4,\n departureTime: '18:25',\n statusText: 'Boarding at Gate 2'\n})\n</script>
`v-bind` is the bridge between reactive state and HTML attributes. A bound attribute can become a URL, a disabled flag, a class map, a style object, or a whole set of attributes expanded from one object.
The shorthand `:` is only syntax sugar; Vue treats it the same as `v-bind`. The important part is that the attribute value stays synchronized with component state instead of being copied once and forgotten.
<template>\n <form class="ticket-form">\n <input\n :type="field.type"\n :placeholder="field.placeholder"\n :disabled="field.locked"\n :aria-label="field.label"\n >\n <button :disabled="field.locked">Update</button>\n </form>\n</template>\n\n<script setup>\nimport { reactive } from 'vue'\n\nconst field = reactive({\n type: 'text',\n placeholder: 'Enter gate code',\n label: 'Gate code field',\n locked: false\n})\n</script>
Template expressions can read values, call simple methods, use ternaries, and access properties. They cannot contain full control flow statements like if, for, or return.
When logic starts to spread across several bindings, Vue code is usually clearer if that logic moves into a computed property or a helper in <script setup>.
Class and style bindings are where template syntax becomes especially practical. Vue lets you feed object and array forms directly into :class and :style, which keeps conditional UI state near the element that uses it.
This is easier to read when the component has a few explicit visual states: selected, delayed, muted, highlighted, or locked.
<template>\n <span\n :class="[\n 'gate-chip',\n { open: gate.isOpen, delayed: gate.delayMinutes > 0 }\n ]"\n :style="{ opacity: gate.isOpen ? 1 : 0.65 }"\n >\n {{ gate.label }}\n </span>\n</template>\n\n<script setup>\nimport { reactive } from 'vue'\n\nconst gate = reactive({\n label: 'Gate 12',\n isOpen: true,\n delayMinutes: 15\n})\n</script>
Vue does not keep parsing the template as the page runs. It compiles the template into a render function once, then lets reactive reads inside that render function determine what must update.
That is why template syntax feels declarative: you describe the desired DOM shape, and Vue performs the bookkeeping for text nodes, attributes, and conditional branches.
<template>\n <article class="departure-panel">\n <h3>{{ departure.name }}</h3>\n <p :class="{ delayed: departure.delayMinutes > 0 }">\n {{ departure.delayMinutes > 0 ? departure.delayMinutes + ' minute delay' : 'On time' }}\n </p>\n <a :href="departure.ticketUrl" :aria-label="'Open ticket for ' + departure.name">\n View ticket\n </a>\n </article>\n</template>\n\n<script setup>\nimport { reactive } from 'vue'\n\nconst departure = reactive({\n name: 'Harbor Express 19',\n delayMinutes: 12,\n ticketUrl: '/tickets/harbor-express-19'\n})\n</script>
Writing `src="{{ imageUrl }}"` or `disabled="{{ locked }}"`.
Use `:src="imageUrl"` and `:disabled="locked"` so Vue owns the live value.
Putting `if`, `for`, or assignment logic directly inside `{{ }}`.
Use a ternary, computed property, or method result that already returns the final value.
Using `v-html` for content that should be safe plain text.
Use interpolation for text and keep `v-html` for trusted markup only.
Text nodes and attributes are different parts of the DOM. Interpolation writes text, while `v-bind` updates attributes or properties.
Vue converts the value to a string for text rendering. That is usually not what you want unless the string form is intentional.
HTML treats the presence of many attributes as truthy. A string like "false" still counts as present, so the binding must pass a boolean value.
Move it when the binding becomes hard to scan or when the same logic appears in several places. That usually means the value belongs in a computed property.
Explore 500+ free tutorials across 20+ languages and frameworks.