Skip to main content

Vue.js v-html Directive

Introduction

In Vue.js applications, we often need to display text content dynamically. The default way to display data in Vue templates is using the mustache syntax ({{ }}), which automatically escapes HTML characters. However, there are scenarios where we need to render raw HTML content. This is where the v-html directive comes into play.

The v-html directive allows you to update the inner HTML of an element with the provided value. It's similar to setting the innerHTML property in vanilla JavaScript, but wrapped in Vue's reactive system.

Basic Syntax

The basic syntax of the v-html directive is straightforward:

html
<element v-html="expression"></element>

Where:

  • element is any valid HTML element
  • expression is a Vue data property or expression that evaluates to a string containing HTML

How v-html Works

Let's look at a simple example to understand how v-html works:

html
<template>
<div>
<p>Using mustaches: {{ rawHtml }}</p>
<p>Using v-html directive: <span v-html="rawHtml"></span></p>
</div>
</template>

<script>
export default {
data() {
return {
rawHtml: '<span style="color: red">This should be red</span>'
}
}
}
</script>

Output:

  • The first paragraph will display the literal string: <span style="color: red">This should be red</span>
  • The second paragraph will render the HTML, showing "This should be red" in red color

v-html vs. Mustache Syntax

To understand when to use v-html over the mustache syntax, let's compare the two:

FeatureMustache Syntax {{ }}v-html Directive
HTML EscapingAutomatically escapes HTMLRenders raw HTML
XSS ProtectionSafer (escapes content)Potentially unsafe
Use CasePlain text displayWhen you need to render HTML markup

Common Use Cases

1. Rendering Rich Text Editor Content

When working with content from rich text editors (like TinyMCE, CKEditor, etc.):

html
<template>
<div>
<h2>Blog Post</h2>
<div class="content" v-html="blogPost.content"></div>
</div>
</template>

<script>
export default {
data() {
return {
blogPost: {
title: 'Learning Vue.js',
content: '<p>This is a <strong>formatted</strong> blog post with <em>rich text</em>.</p>'
}
}
}
}
</script>

2. Displaying API-Returned HTML

When your backend API returns formatted HTML content:

html
<template>
<div>
<div class="api-content" v-html="apiContent"></div>
</div>
</template>

<script>
export default {
data() {
return {
apiContent: ''
}
},
created() {
// Simulating an API call
this.fetchContentFromAPI();
},
methods: {
fetchContentFromAPI() {
// In a real app, this would be an actual API call
setTimeout(() => {
this.apiContent = '<h3>API Response</h3><p>This content came from the server with <strong>HTML formatting</strong>.</p>';
}, 1000);
}
}
}
</script>

3. Dynamically Creating UI Elements

When you need to create UI elements based on data:

html
<template>
<div>
<div v-html="generateStarRating(productRating)"></div>
<p>Rating: {{ productRating }}/5</p>
</div>
</template>

<script>
export default {
data() {
return {
productRating: 4
}
},
methods: {
generateStarRating(rating) {
let stars = '';
// Create filled stars based on rating
for (let i = 0; i < 5; i++) {
if (i < rating) {
stars += '<span style="color: gold; font-size: 24px;">★</span>';
} else {
stars += '<span style="color: gray; font-size: 24px;">★</span>';
}
}
return stars;
}
}
}
</script>

Security Considerations: XSS Vulnerability

warning

The v-html directive can be a security risk if used with untrusted content, as it can lead to Cross-Site Scripting (XSS) attacks.

When using v-html, you're essentially allowing arbitrary HTML to be executed in your application, which can include malicious JavaScript.

Example of potential vulnerability:

html
<template>
<div>
<h3>User Comment:</h3>
<div v-html="userComment"></div>
</div>
</template>

<script>
export default {
data() {
return {
userComment: '<p>Nice product!</p><script>alert("This could be malicious code!")<\/script>'
}
}
}
</script>

Best Practices to Reduce Risk

  1. Only use v-html with trusted content - Avoid using it with user-generated content or any data from external sources
  2. Sanitize HTML content - Use libraries like DOMPurify to clean HTML before rendering:
html
<template>
<div>
<div v-html="sanitizedComment"></div>
</div>
</template>

<script>
import DOMPurify from 'dompurify';

export default {
data() {
return {
userComment: '<p>Nice product!</p><script>alert("Malicious code!")<\/script>'
}
},
computed: {
sanitizedComment() {
return DOMPurify.sanitize(this.userComment);
}
}
}
</script>

Practical Example: Creating a Simple Markdown Preview

Let's create a simple markdown preview application using the v-html directive and a markdown parsing library:

html
<template>
<div class="markdown-editor">
<div class="editor-container">
<h3>Markdown Input</h3>
<textarea
v-model="markdownInput"
placeholder="Write your markdown here..."
rows="10"
class="markdown-textarea"
></textarea>
</div>

<div class="preview-container">
<h3>Preview</h3>
<div
class="markdown-preview"
v-html="markdownOutput"
></div>
</div>
</div>
</template>

<script>
// You would need to install marked: npm install marked
import { marked } from 'marked';

export default {
data() {
return {
markdownInput: '# Hello Vue!\n\nThis is **bold** text and this is *italic* text.\n\n- List item 1\n- List item 2'
}
},
computed: {
markdownOutput() {
// Convert markdown to HTML
return marked(this.markdownInput);
}
}
}
</script>

<style scoped>
.markdown-editor {
display: flex;
gap: 20px;
}

.editor-container, .preview-container {
flex: 1;
}

.markdown-textarea {
width: 100%;
font-family: monospace;
}

.markdown-preview {
padding: 10px;
border: 1px solid #ddd;
min-height: 200px;
}
</style>

This example creates a simple markdown editor where users can type markdown on the left side and immediately see a preview on the right. The marked library converts markdown to HTML, and then we use v-html to render that HTML.

Understanding When Not to Use v-html

While v-html is powerful, there are cases where you should avoid it:

  1. Text-only content: Use mustache syntax ({{ }}) for regular text
  2. Dynamic class or style binding: Use v-bind:class or :class instead
  3. Creating complex components: Consider using proper component architecture instead
  4. Untrusted content: Avoid using v-html with user input

Summary

The v-html directive is a powerful tool in Vue.js that allows you to render HTML content dynamically.

Key takeaways:

  • v-html updates the element's innerHTML with the provided value
  • It's useful when you need to render HTML markup that comes from external sources
  • Always be cautious about security concerns and only use with trusted content
  • When possible, sanitize HTML content with libraries like DOMPurify
  • For most scenarios where you just need to display text, the mustache syntax is safer

Exercises

  1. Create a simple Vue application that fetches an article with HTML formatting from a mock API and displays it using v-html
  2. Build a tooltip component that can display formatted HTML content
  3. Create a "highlight search results" feature that bolds search terms in text using v-html
  4. Implement a simple HTML sanitizer before using content with v-html

Additional Resources



If you spot any mistakes on this website, please let me know at feedback@compilenrun.com. I’d greatly appreciate your feedback! :)