Vize

Vue Rules: Template Safety

HTML content, duplicate attributes, URL safety, and reusable element IDs. See all Vue rules for the complete reference and Vue Rule Options for configurable settings.

vue/no-v-html

Reports v-html because it renders raw HTML and can turn user-controlled content into an XSS sink.

Default severity: warning
Presets: essential, happy-path, nuxt, opinionated

Bad:

<template>
  <article v-html="content" />
</template>

Good:

<template>
  <article>{{ content }}</article>
</template>

vue/no-duplicate-attributes

Reports duplicate attributes on the same element.

Default severity: error
Presets: essential, happy-path, nuxt, opinionated

Bad:

<template>
  <button class="primary" class="large">Save</button>
</template>

Good:

<template>
  <button class="primary large">Save</button>
</template>

vue/no-unsafe-url

Reports URL bindings and static URL attributes that may resolve to unsafe schemes such as javascript:, vbscript:, or executable data: payloads.

Default severity: warning
Presets: essential, happy-path, nuxt, opinionated

Bad:

<template>
  <iframe src="javascript:alert(1)"></iframe>
  <object data="data:text/html,<script>alert(1)</script>"></object>
  <img srcset="/safe.png 1x, javascript:alert(1) 2x" />
  <a :href="nextUrl">Continue</a>
</template>

Good:

<script setup lang="ts">
const rawNextUrl = ref("/next");
const nextUrl = computed(() => {
  return rawNextUrl.value.startsWith("/") ? rawNextUrl.value : "/";
});
</script>

<template>
  <iframe src="/embedded/report" title="Report"></iframe>
  <img srcset="/avatar.png 1x, /[email protected] 2x" />
  <a :href="nextUrl">Continue</a>
</template>

vue/use-unique-element-ids

Reports static literal IDs in places where useId() is safer for component reuse and SSR.

Default severity: warning
Presets: nuxt, opinionated

Bad:

<template>
  <label for="email">Email</label>
  <input id="email" />
</template>

Good:

<script setup lang="ts">
const emailId = useId();
</script>

<template>
  <label :for="emailId">Email</label>
  <input :id="emailId" />
</template>

vue/no-textarea-mustache

Reports mustache interpolation inside <textarea>. The text does not bind the control. Use v-model.

Default severity: error
Presets: essential, happy-path, nuxt, opinionated

Bad:

<template>
  <textarea>{{ message }}</textarea>
</template>

Good:

<template>
  <textarea v-model="message"></textarea>
</template>

vue/no-v-text-v-html-on-component

Reports v-text or v-html on a component. Those directives replace the component's own output. A native element, or <component is="div">, may use them.

Default severity: error
Presets: essential, happy-path, nuxt, opinionated

Bad:

<template>
  <MyComponent v-html="content" />
  <MyComponent v-text="content" />
</template>

Good:

<template>
  <div v-html="content"></div>
  <component is="div" v-html="content" />
  <MyComponent>{{ content }}</MyComponent>
</template>

vue/permitted-contents

Reports HTML nesting the parser or the content model forbids, so the DOM and the virtual DOM would disagree. An unresolved component, a slot, or a dynamic binding stays silent rather than guessing.

Default severity: error
Presets: happy-path, nuxt, opinionated

Bad:

<template>
  <p><div>block in a paragraph</div></p>
  <table><tr><td>row without tbody</td></tr></table>
  <a href="#"><button type="button">nested control</button></a>
  <ul><div>not a list item</div></ul>
</template>

Good:

<template>
  <p><span>inline in a paragraph</span></p>
  <table><tbody><tr><td>cell</td></tr></tbody></table>
  <ul><li>list item</li><MyItem /></ul>
</template>