Vue Rules: Template Safety
Every rule on this page includes its purpose, prerequisites, configuration, and complete Bad/Good examples. Highlighted lines show the changes; copied code keeps the complete source. Each packet states its current support limits.
| Rule | Examples | Purpose |
|---|---|---|
vue/no-duplicate-attributes |
Bad · Good | Disallow duplicate attributes on the same element |
vue/no-textarea-mustache |
Bad · Good | Disallow mustache interpolation in <textarea> |
vue/no-unsafe-url |
Bad · Good | Warn about potentially unsafe URL bindings |
vue/no-v-html |
Bad · Good | Warn against v-html to prevent XSS vulnerabilities |
vue/no-v-text-v-html-on-component |
Bad · Good | Disallow v-text / v-html on component elements |
vue/permitted-contents |
Bad · Good | Enforce HTML content model rules |
vue/use-unique-element-ids |
Bad · Good | Enforce unique element IDs using useId() instead of static literals |
All rules · Rule Options · ESLint migration map · Project checks · Attributes across components
vue/no-duplicate-attributes
Disallow duplicate attributes on the same element
Default severity: error
Presets: essential, happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/no-duplicate-attributes": "error"
}
},
},
});
vp run lint
Bad
The same button declares class twice instead of one combined class value.
<template>
<button class="primary" class="large">Save</button>
</template>
Good
Both class tokens appear in a single class attribute.
<template>
<button class="primary large">Save</button>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/no-textarea-mustache
Disallow mustache interpolation in <textarea>
Default severity: error
Presets: essential, happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/no-textarea-mustache": "error"
}
},
},
});
vp run lint
Bad
The textarea places message in child interpolation instead of binding its value.
<template>
<textarea>{{ message }}</textarea>
</template>
Good
v-model binds the editable textarea value to message.
<template>
<textarea v-model="message"></textarea>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/no-unsafe-url
Warn about potentially unsafe URL bindings
Default severity: warning
Presets: essential, happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/no-unsafe-url": "warn"
}
},
},
});
vp run lint
Bad
The anchor destination begins with the executable javascript: scheme.
<template>
<a href="javascript:alert(1)">Continue</a>
</template>
Good
The anchor uses the ordinary local /next navigation destination.
<template>
<a href="/next">Continue</a>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/no-v-html
Warn against v-html to prevent XSS vulnerabilities
Default severity: warning
Presets: essential, happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/no-v-html": "warn"
}
},
},
});
vp run lint
Bad
v-html interprets content as HTML rather than ordinary text.
<template>
<article v-html="content" />
</template>
Good
Mustache interpolation displays content as escaped text instead of injecting HTML.
<template>
<article>{{ content }}</article>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/no-v-text-v-html-on-component
Disallow v-text / v-html on component elements
Default severity: error
Presets: essential, happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/no-v-text-v-html-on-component": "error"
}
},
},
});
vp run lint
Bad
The component tag receives v-html or v-text, which replaces element content rather than supplying component slots.
<template>
<MyComponent v-html="content" />
<MyComponent v-text="content" />
</template>
Good
Native HTML targets can receive the directives; MyComponent receives its content through the default slot.
<template>
<div v-html="content"></div>
<component is="div" v-html="content" />
<MyComponent>{{ content }}</MyComponent>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/permitted-contents
Enforce HTML content model rules
Default severity: error
Presets: happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/permitted-contents": "error"
}
},
},
});
vp run lint
Bad
The examples put block content in p, omit the table body, nest interactive controls, or put a div directly inside ul.
<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
The examples use inline paragraph content, an explicit tbody, and li children. The custom MyItem is not treated as a known native ul child.
<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>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/use-unique-element-ids
Enforce unique element IDs using useId() instead of static literals
Default severity: warning
Presets: nuxt, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/use-unique-element-ids": "warn"
}
},
},
});
vp run lint
Bad
The literal email ID is reused by every instance of this component, which can misdirect its label when several instances are rendered.
<template>
<label for="email">Email</label>
<input id="email" />
</template>
Good
useId() produces the instance's emailId; bind the same value to the label's for and the input's id.
<script setup>
import { useId } from "vue";
const emailId = useId();
</script>
<template>
<label :for="emailId">Email</label>
<input :id="emailId" />
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.