skip to content

Interpolation & Expressions

Mustache interpolation, v-html and the single-expression rule, plus how a directive's argument, modifiers and value are parsed. Interviewers probe the v-html XSS risk and dynamic arguments.

part ofVue.jsoverview, primer and where to startread it →
on this pageshow

explore

questions

5

In Vue 3, how do {{ }} interpolation, v-text and v-html differ in what they write, and what does v-html content skip?

level: juniorimportance: must knowfreq 70%

answer

  1. text versus markup
  2. part of the content or all of it
  3. textContent and innerHTML
  4. not compiled by Vue

basics

~20 s

Vue's {{ }} inserts escaped text into part of an element, v-text replaces the whole textContent, and v-html replaces innerHTML with raw markup that Vue never compiles, so it holds no bindings or components and is an XSS risk.

solid answer

~40 s

`{{ expr }}` renders the value as **escaped text** wherever it appears, so it can sit beside static text. `v-text="expr"` sets the element's `textContent`, replacing **all** of its content; it is escaped too. `v-html="expr"` sets `innerHTML`, so the string is parsed as **real HTML**. That HTML is **not processed by Vue's template compiler**: mustaches inside it stay literal, directives and components do not work, and in an SFC `scoped` styles do not reach it. The compiler reports `v-html will override element children.` (or the `v-text` equivalent) if the element also has children. Because it is raw markup, `v-html` is an XSS sink: the docs say to use it only on trusted content and never on user-provided content.

code

vue · 12 lines
vue
<script setup lang="ts">
import { ref } from 'vue'

const name = ref('<b>Ada</b>')
const trustedHtml = ref('<em>Built at release time</em> {{ ignored }}')
</script>

<template>
  <p>Hello, {{ name }}!</p>
  <p v-text="name"></p>
  <p v-html="trustedHtml"></p>
</template>

go deeper

for a junior

Remember that {{ }} and v-text render escaped text, v-html renders raw HTML, and attributes need v-bind rather than mustaches.

for a middle

Explain that v-html bypasses the template compiler, so no bindings, components or scoped styles apply to its content.

for a senior

Treat every v-html as a security review point: confirm the source is trusted or sanitized and prefer components for anything interactive.

for a principal

Set a policy that v-html needs a documented trusted source, and flag new uses in review or linting.

## Three ways to put a value into an element Vue templates offer three ways to render a value as content, and they differ in **what they write** and **how much of the element they own**. | Syntax | Writes | Escaped? | Owns the whole element? | |---|---|---|---| | `{{ expr }}` | a text node | yes | no, can mix with static text | | `v-text="expr"` | `textContent` | yes | yes, replaces all content | | `v-html="expr"` | `innerHTML` | no, parsed as HTML | yes, replaces all content | ## Mustache interpolation Double curly braces are the everyday binding. `<span>Hello, {{ name }}!</span>` keeps the static text and updates only the interpolated part when `name` changes. The value is converted to a string and rendered as **text**, so a string such as `<b>hi</b>` shows the angle brackets literally. Vue's security guide notes that this escaping uses native browser APIs such as `textContent`. Mustaches only work in **text content**, not in attribute values; attributes use `v-bind` (`:title="expr"`). ## v-text `v-text` is the directive form of the same thing, but it sets the element's `textContent`, so anything already inside the element is **overwritten**. `<span v-text="msg"></span>` equals `<span>{{ msg }}</span>`. It is rarely needed; its main use is when you want the element to be entirely owned by one value. If the element has children as well, the compiler reports `v-text will override element children.` ## v-html `v-html` sets `innerHTML`, so the browser parses the string as markup. Three consequences follow directly from the fact that Vue's **template compiler never sees that string**: 1. **No Vue syntax inside it.** Mustaches remain literal text, and `v-` attributes, `@click` handlers and component tags are not processed. You cannot build template partials out of strings with `v-html`; the docs point to components instead. 2. **No scoped styles.** In an SFC, `scoped` CSS works by stamping compiled elements with an attribute; the injected markup never gets that attribute, so scoped rules do not match it. 3. **Security.** Because the markup is live, any attacker-controlled HTML can carry script through event-handler attributes and similar vectors. The API reference says to use `v-html` only on **trusted** content and **never** on user-provided content. Like `v-text`, `v-html` replaces children, and the compiler reports `v-html will override element children.` if you give the element both. ## Choosing between them - Use **mustaches** for almost everything, including values that might contain `<` or `&`. - Use **`v-text`** only when one value should own the whole element; it offers no safety advantage over mustaches. - Use **`v-html`** only for HTML you trust, such as markup generated by your own build step or content sanitized with an allowlist before it reaches the component. ## Common misconceptions - Thinking `v-html` "renders a template": it renders HTML, and Vue ignores everything Vue-specific in it. - Thinking `v-text` is safer than `{{ }}`: both are escaped text. - Thinking mustaches work inside attributes: they do not; use `:attr`. - Expecting a component tag such as `<MyBadge />` inside a `v-html` string to render: the browser sees an unknown element and Vue never mounts it.

  • Why does a component tag inside a Vue 3 `v-html` string not render the component?
    `v-html` hands the string to `innerHTML`, so the browser parses it as plain HTML. Vue's compiler never sees it, so the tag is just an unknown element and no component is mounted. Render trusted structure with real components in the template instead.
  • Is `v-text` in Vue 3 safer than `{{ }}` interpolation?
    No. Both render escaped text. The difference is scope: `v-text` sets `textContent` and replaces everything inside the element, while a mustache can sit alongside static text.

saying these in an interview costs you the question

  • v-html compiles its string as a Vue template.
  • v-text is safer than mustache interpolation.
  • Mustaches can be used inside attribute values.
  • Scoped styles apply to markup injected with v-html.
  • v-html is fine for user-provided content if it looks harmless.
open as a page

A Vue 3 page must render rich text from a CMS that users can edit; which rendering approaches are unsafe, and what would you ship instead?

level: seniorimportance: must knowfreq 55%

basics

~20 s

In Vue 3, v-html on unsanitized CMS HTML, compiling CMS strings as Vue templates, and mounting Vue over server-rendered user content are all unsafe. Sanitize with an allowlist before v-html, or render structured content through your own components.

open as a page

In a Vue 3 directive such as v-on:[eventName].once="handler", what are its parts, and what rules apply to a dynamic [argument]?

level: middleimportance: should knowfreq 38%

basics

~10 s

A Vue 3 directive reads name:argument.modifiers="value"; a bracketed argument is a JavaScript expression that must evaluate to a string, or null to remove the binding, and cannot contain spaces or quotes.

open as a page

In Vue 3, why does a template expression such as {{ window.innerWidth }} fail, and what may a template expression contain?

level: middleimportance: should knowfreq 45%

basics

~20 s

Vue 3 template expressions see only the component's scope plus a restricted globals allowlist (Math, Date, JSON and similar); window is not on it, so it resolves to undefined. Each binding must be one expression, not statements.

open as a page

In Vue 3, what do the v-pre and v-cloak directives do, and why does a build-step SFC app not need v-cloak?

level: juniorimportance: nice to knowfreq 24%

basics

~20 s

Vue's v-pre skips compiling an element and its children, so mustaches render literally; v-cloak stays on an element until its component mounts, letting CSS hide raw in-DOM templates. SFCs are precompiled, so no raw template ever flashes.

open as a page