diff --git a/docs/rules/index.md b/docs/rules/index.md
index eca5a3191..052df19bc 100644
--- a/docs/rules/index.md
+++ b/docs/rules/index.md
@@ -118,6 +118,7 @@ Rules in this category are enabled for all presets provided by eslint-plugin-vue
| [vue/valid-v-else](./valid-v-else.md) | enforce valid `v-else` directives | | :three::two::warning: |
| [vue/valid-v-for](./valid-v-for.md) | enforce valid `v-for` directives | | :three::two::warning: |
| [vue/valid-v-html](./valid-v-html.md) | enforce valid `v-html` directives | | :three::two::warning: |
+| [vue/valid-v-if-template-root](./valid-v-if-template-root.md) | enforce valid `v-if` directives on root element | | :three::two::warning: |
| [vue/valid-v-if](./valid-v-if.md) | enforce valid `v-if` directives | | :three::two::warning: |
| [vue/valid-v-is](./valid-v-is.md) | enforce valid `v-is` directives | | :three::warning: |
| [vue/valid-v-memo](./valid-v-memo.md) | enforce valid `v-memo` directives | | :three::warning: |
diff --git a/docs/rules/valid-template-root.md b/docs/rules/valid-template-root.md
index 14a56edc0..144581a1c 100644
--- a/docs/rules/valid-template-root.md
+++ b/docs/rules/valid-template-root.md
@@ -46,4 +46,4 @@ This rule was introduced in eslint-plugin-vue v3.11.0
## :mag: Implementation
- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/valid-template-root.js)
-- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/valid-template-root.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/valid-template-root.js)
\ No newline at end of file
diff --git a/docs/rules/valid-v-if-template-root.md b/docs/rules/valid-v-if-template-root.md
new file mode 100644
index 000000000..c1adfb7ad
--- /dev/null
+++ b/docs/rules/valid-v-if-template-root.md
@@ -0,0 +1,40 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/valid-v-if-template-root
+description: enforce valid `v-if` directives on root element
+---
+# vue/valid-v-if-template-root
+
+> enforce valid `v-if` directives on root element
+
+- :exclamation: ***This rule has not been released yet.***
+- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/essential"`, `"plugin:vue/vue3-strongly-recommended"`, `"plugin:vue/strongly-recommended"`, `"plugin:vue/vue3-recommended"` and `"plugin:vue/recommended"`.
+
+This rule checks whether every template root is valid.
+
+## :book: Rule Details
+
+This rule reports the template root in the following cases:
+
+
+
+```vue
+
+
+
+
+
+
+```
+
+
+
+## :wrench: Options
+
+Nothing.
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/valid-v-if-template-root.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/valid-v-if-template-root.js)
diff --git a/lib/configs/essential.js b/lib/configs/essential.js
index 944a162ea..d89aaf3cc 100644
--- a/lib/configs/essential.js
+++ b/lib/configs/essential.js
@@ -62,6 +62,7 @@ module.exports = {
'vue/valid-v-else': 'error',
'vue/valid-v-for': 'error',
'vue/valid-v-html': 'error',
+ 'vue/valid-v-if-template-root': 'error',
'vue/valid-v-if': 'error',
'vue/valid-v-model': 'error',
'vue/valid-v-on': 'error',
diff --git a/lib/configs/vue3-essential.js b/lib/configs/vue3-essential.js
index 5fe530beb..4702f2c94 100644
--- a/lib/configs/vue3-essential.js
+++ b/lib/configs/vue3-essential.js
@@ -77,6 +77,7 @@ module.exports = {
'vue/valid-v-else': 'error',
'vue/valid-v-for': 'error',
'vue/valid-v-html': 'error',
+ 'vue/valid-v-if-template-root': 'error',
'vue/valid-v-if': 'error',
'vue/valid-v-is': 'error',
'vue/valid-v-memo': 'error',
diff --git a/lib/index.js b/lib/index.js
index 879a49056..04f8b64d6 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -220,6 +220,7 @@ module.exports = {
'valid-v-else': require('./rules/valid-v-else'),
'valid-v-for': require('./rules/valid-v-for'),
'valid-v-html': require('./rules/valid-v-html'),
+ 'valid-v-if-template-root': require('./rules/valid-v-if-template-root'),
'valid-v-if': require('./rules/valid-v-if'),
'valid-v-is': require('./rules/valid-v-is'),
'valid-v-memo': require('./rules/valid-v-memo'),
diff --git a/lib/rules/valid-v-if-template-root.js b/lib/rules/valid-v-if-template-root.js
new file mode 100644
index 000000000..2cd62cfe2
--- /dev/null
+++ b/lib/rules/valid-v-if-template-root.js
@@ -0,0 +1,72 @@
+/**
+ * @author Perry Song
+ * @copyright 2023 Perry Song. All rights reserved.
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+const utils = require('../utils')
+
+/**
+ * Get the number of root element directive
+ * @param {VNode[]} rootElements The start tag node to check.
+ * @param {string} directiveName The directive name to check.
+ */
+function getDirectiveLength(rootElements, directiveName) {
+ if (!directiveName) return 0
+ return rootElements.filter(
+ (element) =>
+ element.type === 'VElement' && utils.hasDirective(element, directiveName)
+ ).length
+}
+
+module.exports = {
+ meta: {
+ type: 'problem',
+ docs: {
+ description: 'enforce valid `v-if` directives on root element',
+ categories: ['vue3-essential', 'essential'],
+ url: 'https://eslint.vuejs.org/rules/valid-v-if-template-root.html'
+ },
+ fixable: null,
+ schema: []
+ },
+ /** @param {RuleContext} context */
+ create(context) {
+ const sourceCode = context.getSourceCode()
+
+ return {
+ /** @param {Program} program */
+ Program(program) {
+ const element = program.templateBody
+ if (element == null) {
+ return
+ }
+
+ const rootElements = []
+ for (const child of element.children) {
+ if (sourceCode.getText(child).trim() !== '') {
+ rootElements.push(child)
+ }
+ }
+
+ if (rootElements.length === 0) return
+ const hasRootVIfLength = getDirectiveLength(rootElements, 'if')
+ const hasRootVElseLength = getDirectiveLength(rootElements, 'else')
+ const hasRootVElseIfLength = getDirectiveLength(rootElements, 'else-if')
+ if (
+ hasRootVIfLength === 1 &&
+ hasRootVElseLength === 0 &&
+ hasRootVElseIfLength === 0
+ ) {
+ context.report({
+ node: element,
+ loc: element.loc,
+ message:
+ '`v-if` should not be used on root element without `v-else`.'
+ })
+ }
+ }
+ }
+ }
+}
diff --git a/tests/lib/rules/valid-v-if-template-root.js b/tests/lib/rules/valid-v-if-template-root.js
new file mode 100644
index 000000000..bca90aebd
--- /dev/null
+++ b/tests/lib/rules/valid-v-if-template-root.js
@@ -0,0 +1,111 @@
+/**
+ * @author Perry Song
+ * @copyright 2023 Perry Song. All rights reserved.
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+const RuleTester = require('eslint').RuleTester
+const rule = require('../../../lib/rules/valid-v-if-template-root')
+
+const tester = new RuleTester({
+ parser: require.resolve('vue-eslint-parser'),
+ parserOptions: { ecmaVersion: 2015 }
+})
+
+tester.run('valid-v-if-template-root', rule, {
+ valid: [
+ {
+ filename: 'test.vue',
+ code: ''
+ },
+ {
+ filename: 'test.vue',
+ code: 'abc
'
+ },
+ {
+ filename: 'test.vue',
+ code: '\n abc
\n'
+ },
+ {
+ filename: 'test.vue',
+ code: '\n \n abc
\n'
+ },
+ {
+ filename: 'test.vue',
+ code: '\n \n abc
\n abc
\n'
+ },
+ {
+ filename: 'test.vue',
+ code: '\n \n abc
\n abc
\n abc
\n'
+ },
+ {
+ filename: 'test.vue',
+ code: `\n \n \n \n`
+ },
+ {
+ filename: 'test.vue',
+ code: ''
+ },
+ {
+ filename: 'test.vue',
+ code: ''
+ },
+ {
+ filename: 'test.vue',
+ code: 'test
'
+ },
+ {
+ filename: 'test.vue',
+ code: ''
+ },
+ {
+ filename: 'test.vue',
+ code: ''
+ },
+ {
+ filename: 'test.vue',
+ code: '\n \n \n'
+ },
+ {
+ filename: 'test.vue',
+ code: '{{a b c}}'
+ },
+ {
+ filename: 'test.vue',
+ code: 'aaaaaa'
+ },
+ {
+ filename: 'test.vue',
+ code: 'aaaaaa'
+ },
+ {
+ filename: 'test.vue',
+ code: ''
+ },
+ {
+ filename: 'test.vue',
+ code: ''
+ },
+ {
+ filename: 'test.vue',
+ code: ''
+ },
+ {
+ filename: 'test.vue',
+ code: ' '
+ }
+ ],
+ invalid: [
+ {
+ filename: 'test.vue',
+ code: '',
+ errors: ['`v-if` should not be used on root element without `v-else`.']
+ },
+ {
+ filename: 'test.vue',
+ code: '',
+ errors: ['`v-if` should not be used on root element without `v-else`.']
+ }
+ ]
+})