Vue Rules: Template Structure
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-child-content |
Bad · Good | Disallow child content when using v-html or v-text |
vue/no-dupe-v-else-if |
Bad · Good | Disallow duplicate conditions in v-if / v-else-if chains |
vue/no-lone-template |
Bad · Good | Disallow unnecessary <template> elements |
vue/no-template-key |
Bad · Good | Disallow key attribute on <template> |
vue/no-template-shadow |
Bad · Good | Disallow variable names that shadow variables in outer scope |
vue/no-unused-vars |
Bad · Good | Disallow unused variable definitions in v-for and v-slot directives |
vue/no-use-v-if-with-v-for |
Bad · Good | Disallow using v-if on the same element as v-for |
vue/no-useless-template-attributes |
Bad · Good | Disallow useless attributes on <template> elements |
vue/require-v-for-key |
Bad · Good | Require v-bind:key with v-for directives |
All rules · Rule Options · ESLint migration map · Project checks · Attributes across components
vue/no-child-content
Disallow child content when using v-html or v-text
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-child-content": "error"
}
},
},
});
vp run lint
Bad
v-text replaces the paragraph content, so the authored fallback text cannot survive that directive.
<template>
<p v-text="message">Fallback text</p>
</template>
Good
Removing the child text leaves v-text as the single source of paragraph content.
<template>
<p v-text="message" />
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/no-dupe-v-else-if
Disallow duplicate conditions in v-if / v-else-if chains
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-dupe-v-else-if": "error"
}
},
},
});
vp run lint
Bad
The else-if repeats the ready condition already tested by the first branch, making that later branch unreachable.
<template>
<p v-if="status === 'ready'">Ready</p>
<p v-else-if="status === 'ready'">Still ready</p>
</template>
Good
The second branch tests loading, a distinct state that can reach the else-if.
<template>
<p v-if="status === 'ready'">Ready</p>
<p v-else-if="status === 'loading'">Loading</p>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/no-lone-template
Disallow unnecessary <template> elements
Default severity: warning
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/no-lone-template": "warn"
}
},
},
});
vp run lint
Bad
The inner template has no directive or slot role that gives it a structural purpose.
<template>
<div><template><p>Details</p></template></div>
</template>
Good
Removing the unnecessary wrapper leaves the paragraph directly inside div.
<template>
<div><p>Details</p></div>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/no-template-key
Disallow key attribute on <template>
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-template-key": "error"
}
},
},
});
vp run lint
Bad
A non-loop template wrapper has a key even though it is not the keyed iteration boundary.
<template>
<template :key="section"><div>Details</div></template>
</template>
Good
The key belongs to a template v-for iteration, where it identifies each repeated fragment.
<template>
<template v-for="item in items" :key="item.id"><div>{{ item.name }}</div></template>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/no-template-shadow
Disallow variable names that shadow variables in outer scope
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.
The current check compares nested v-for bindings. It does not report a single v-for binding merely because it shares a script binding's name.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/no-template-shadow": "warn"
}
},
},
});
vp run lint
Bad
The inner v-for declares item again and hides the outer item binding inside the nested loop.
<template>
<div v-for="item in items" :key="item.id"><span v-for="item in item.children" :key="item.id">{{ item.name }}</span></div>
</template>
Good
The inner loop declares child, leaving item available for the outer row and child for the nested row.
<template>
<div v-for="item in items" :key="item.id"><span v-for="child in item.children" :key="child.id">{{ child.name }}</span></div>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/no-unused-vars
Disallow unused variable definitions in v-for and v-slot directives
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-unused-vars": "warn"
}
},
},
});
vp run lint
Bad
The loop declares an unused index and the slot declares foo without referencing it.
<template>
<li v-for="(item, index) in items" :key="item.id">{{ item.name }}</li>
<template v-slot="{ foo }">
<span>Hello</span>
</template>
</template>
Good
The examples consume index or mark it intentionally unused as _index, and the slot renders data. Index keys are only a usage example here, not a recommendation for stable item identity.
<template>
<li v-for="(item, index) in items" :key="index">{{ item.name }}</li>
<li v-for="(item, _index) in items" :key="item.id">{{ item.name }}</li>
<template v-slot="{ data }">
<span>{{ data }}</span>
</template>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/no-use-v-if-with-v-for
Disallow using v-if on the same element as v-for
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-use-v-if-with-v-for": "warn"
}
},
},
});
vp run lint
Bad
The same list element combines v-if and v-for and tests visibility through the loop binding.
<template>
<li v-for="item in items" v-if="item.visible" :key="item.id">
{{ item.name }}
</li>
</template>
Good
A computed collection filters the visible items before the template iterates over them.
<script setup lang="ts">
const visibleItems = computed(() => items.filter((item) => item.visible));
</script>
<template>
<li v-for="item in visibleItems" :key="item.id">
{{ item.name }}
</li>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/no-useless-template-attributes
Disallow useless attributes on <template> 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-useless-template-attributes": "error"
}
},
},
});
vp run lint
Bad
The conditional template has a class, but this structural wrapper does not render a DOM element to receive it.
<template>
<section><template v-if="ready" class="notice"><p>Ready</p></template></section>
</template>
Good
The class moves to the paragraph that actually renders while v-if stays on the structural template.
<template>
<section><template v-if="ready"><p class="notice">Ready</p></template></section>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/require-v-for-key
Require v-bind:key with v-for directives
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/require-v-for-key": "error"
}
},
},
});
vp run lint
Bad
Each repeated <li> lacks a key that identifies its corresponding item during list updates.
<template>
<li v-for="item in items">{{ item.name }}</li>
</template>
Good
:key="item.id" gives each repeated node the item's identity rather than its current position.
<template>
<li v-for="item in items" :key="item.id">{{ item.name }}</li>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.