Chapter 85: Vue v-text Directive
V-text
This directive is not one of the flashy ones like v-for, v-model or v-if — it doesn’t get much spotlight, but it solves a very specific, very real problem in a clean and safe way.
What does v-text actually do?
v-text tells Vue:
“Take the value of this expression and set it as the textContent of this element — nothing else. Do not interpret it as HTML, do not allow any child elements, do not let mustache {{ }} syntax leak through.”
In plain words:
- v-text = element.textContent = value
- It completely replaces whatever was inside the tag (children, text, other directives) with plain text
- It escapes any HTML characters → safe against XSS when showing user-generated content
v-text vs {{ mustache interpolation }} vs v-html
This is the comparison table you should keep in your head forever:
| Syntax | What actually happens in DOM | Escapes HTML? | Can contain child elements? | When to use it (2026 rule of thumb) |
|---|---|---|---|---|
| {{ userInput }} | <p>Hello <strong>world</strong></p> | Yes | No (text only) | Most normal text interpolation |
| <p v-text=”userInput”></p> | <p>Hello <strong>world</strong></p> | Yes | No (replaces everything) | You want to be extra safe with user content |
| <p v-html=”userInput”></p> | <p>Hello <strong>world</strong></p> | No | Yes (parses HTML) | Trusted rich text from your own backend/CMS |
| <p>{{ userInput }}</p> + userInput = <script>alert(1)</script> | <p><script>alert(1)</script></p> | Yes | No | Default safe choice |
Golden security rule (memorize this)
- Use {{ }} or v-text → always safe (HTML is escaped)
- Use v-html → only if the content is 100% trusted (your own server, sanitized markdown, admin-controlled rich text)
Real, Practical Examples
Example 1 – Showing user comment safely (most common real use-case)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
<template> <div class="comment-thread"> <h3>Comments</h3> <div v-for="comment in comments" :key="comment.id" class="comment"> <!-- Safe: user can type anything, even <script> or <img onerror=...> --> <p v-text="comment.text"></p> <!-- Alternative (same result) --> <!-- <p>{{ comment.text }}</p> --> </div> </div> </template> <script setup lang="ts"> import { ref } from 'vue' const comments = ref([ { id: 1, text: 'Great article! 😊' }, { id: 2, text: 'I <3 Vue 3' }, { id: 3, text: '<script>alert("hacked")</script> nice try' } // ← user tried XSS ]) </script> <style scoped> .comment { padding: 1rem; background: #f8fafc; border-radius: 8px; margin-bottom: 1rem; border-left: 4px solid #3b82f6; } </style> |
Result in browser:
|
0 1 2 3 4 5 6 7 8 |
Great article! 😊 I <3 Vue 3 <script>alert("hacked")</script> nice try |
→ The script tag is shown as plain text — no alert pops up → safe
Example 2 – v-text vs {{ }} vs v-html side-by-side
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
<template> <div class="demo-box"> <h3>Input something dangerous:</h3> <input v-model="dangerousText" placeholder="Try <script>alert(1)</script>" /> <div class="comparison"> <section> <h4>Normal interpolation {{ }}</h4> <p>{{ dangerousText }}</p> </section> <section> <h4>v-text (same as {{ }} but replaces children)</h4> <p v-text="dangerousText"></p> </section> <section class="danger-zone"> <h4>v-html (DANGEROUS – only for trusted content!)</h4> <p v-html="dangerousText"></p> <p class="warning">↑ Script will execute if you type one!</p> </section> </div> </div> </template> <script setup lang="ts"> import { ref } from 'vue' const dangerousText = ref('') </script> <style scoped> .demo-box { max-width: 700px; margin: 3rem auto; padding: 2rem; background: white; border-radius: 12px; box-shadow: 0 10px 30px rgba(0,0,0,0.08); } .comparison { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 2rem; margin-top: 2rem; } section { padding: 1.5rem; border: 1px solid #e5e7eb; border-radius: 8px; } .danger-zone { border-color: #ef4444; background: #fef2f2; } .warning { color: #dc2626; font-weight: bold; margin-top: 1rem; } </style> |
Try typing this into the input:
|
0 1 2 3 4 5 6 |
Hello <strong>world</strong> <script>alert('XSS!')</script> |
- {{ }} and v-text → safe: shows tags as text
- v-html → dangerous: executes the script → alert pops up
Quick Summary Table – v-text in 2026
| Question | Answer / Best Practice |
|---|---|
| What does it do? | Sets element.textContent = value — replaces all children with plain text |
| Escapes HTML? | Yes — <, >, & become <, >, & |
| Can contain child elements? | No — everything inside the tag is replaced |
| vs {{ mustache }} | Almost identical result — v-text replaces children, mustache only replaces text node |
| vs v-html | v-text = safe text, v-html = dangerous raw HTML |
| Performance? | Tiny gain over {{ }} (no need to parse mustache) |
| Still useful in 2026? | Yes — especially when showing user-generated text safely or avoiding mustache parsing issues |
Pro Tips from Real Projects (Hyderabad 2026)
- Prefer {{ text }} for most cases — shorter & more readable
- Use v-text when:
- You want to be extra explicit about “this is plain text only”
- You have an element that must not have children (security paranoia)
- You are showing content that might contain literal {{ }} and don’t want Vue to parse it
- Never use v-html unless content is 100% trusted (your own backend, sanitized markdown)
- In Vue Devtools → you can see exactly what text was set via v-text
- For code examples → prefer <pre v-pre><code>…</code></pre> instead of v-text (shows directives literally)
Your mini practice task:
- Create a comment list component
- Show each comment with v-text=”comment.text” (safe against XSS)
- Try typing <script>alert(1)</script> as a comment → see it displayed as text
- Compare with v-html version → see the danger
Any part confusing? Want full examples for:
- v-text vs {{ }} vs v-html security comparison table in action?
- v-text inside a user comment system (safe way)?
- v-text + v-pre + <pre><code> for code display?
- When to choose v-text over mustache interpolation?
Just tell me — we’ll build the next safe text-display component together 🚀
