diff --git a/docs/rules/index.md b/docs/rules/index.md
index eca5a3191..abc3419a4 100644
--- a/docs/rules/index.md
+++ b/docs/rules/index.md
@@ -240,6 +240,7 @@ For example:
| [vue/no-restricted-props](./no-restricted-props.md) | disallow specific props | :bulb: | :hammer: |
| [vue/no-restricted-static-attribute](./no-restricted-static-attribute.md) | disallow specific attribute | | :hammer: |
| [vue/no-restricted-v-bind](./no-restricted-v-bind.md) | disallow specific argument in `v-bind` | | :hammer: |
+| [vue/no-root-v-if](./no-root-v-if.md) | disallow `v-if` directives on root element | | :hammer: |
| [vue/no-static-inline-styles](./no-static-inline-styles.md) | disallow static inline `style` attributes | | :hammer: |
| [vue/no-template-target-blank](./no-template-target-blank.md) | disallow target="_blank" attribute without rel="noopener noreferrer" | :bulb: | :warning: |
| [vue/no-this-in-before-route-enter](./no-this-in-before-route-enter.md) | disallow `this` usage in a `beforeRouteEnter` method | | :warning: |
diff --git a/docs/rules/no-root-v-if.md b/docs/rules/no-root-v-if.md
new file mode 100644
index 000000000..0899dc036
--- /dev/null
+++ b/docs/rules/no-root-v-if.md
@@ -0,0 +1,37 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/no-root-v-if
+description: disallow `v-if` directives on root element
+---
+
+# vue/no-root-v-if
+
+> disallow `v-if` directives on root element
+
+- :exclamation: ***This rule has not been released yet.***
+
+This rule reports template roots with `v-if`. Rendering of the whole component could be made conditional in the parent component (with a `v-if` there) instead.
+
+## :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/no-root-v-if.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-root-v-if.js)
diff --git a/lib/index.js b/lib/index.js
index 879a49056..c7b580868 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -124,6 +124,7 @@ module.exports = {
'no-restricted-static-attribute': require('./rules/no-restricted-static-attribute'),
'no-restricted-syntax': require('./rules/no-restricted-syntax'),
'no-restricted-v-bind': require('./rules/no-restricted-v-bind'),
+ 'no-root-v-if': require('./rules/no-root-v-if'),
'no-setup-props-destructure': require('./rules/no-setup-props-destructure'),
'no-shared-component-data': require('./rules/no-shared-component-data'),
'no-side-effects-in-computed-properties': require('./rules/no-side-effects-in-computed-properties'),
diff --git a/lib/rules/no-root-v-if.js b/lib/rules/no-root-v-if.js
new file mode 100644
index 000000000..304ef3f12
--- /dev/null
+++ b/lib/rules/no-root-v-if.js
@@ -0,0 +1,46 @@
+/**
+ * @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')
+
+module.exports = {
+ meta: {
+ type: 'suggestion',
+ docs: {
+ description: 'disallow `v-if` directives on root element',
+ categories: undefined,
+ url: 'https://eslint.vuejs.org/rules/no-root-v-if.html'
+ },
+ fixable: null,
+ schema: []
+ },
+ /** @param {RuleContext} context */
+ create(context) {
+ return {
+ /** @param {Program} program */
+ Program(program) {
+ const element = program.templateBody
+ if (element == null) {
+ return
+ }
+
+ const rootElements = element.children.filter(utils.isVElement)
+ if (
+ rootElements.length === 1 &&
+ utils.hasDirective(rootElements[0], 'if')
+ ) {
+ 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/no-root-v-if.js b/tests/lib/rules/no-root-v-if.js
new file mode 100644
index 000000000..766c5b2ad
--- /dev/null
+++ b/tests/lib/rules/no-root-v-if.js
@@ -0,0 +1,115 @@
+/**
+ * @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/no-root-v-if')
+
+const tester = new RuleTester({
+ parser: require.resolve('vue-eslint-parser'),
+ parserOptions: { ecmaVersion: 2015 }
+})
+
+tester.run('no-root-v-if', 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: ' '
+ },
+ {
+ 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`.']
+ }
+ ]
+})