diff --git a/docs/rules/require-explicit-emits.md b/docs/rules/require-explicit-emits.md
index f7ce60dd6..90cb03edb 100644
--- a/docs/rules/require-explicit-emits.md
+++ b/docs/rules/require-explicit-emits.md
@@ -112,6 +112,7 @@ export default {
## :couple: Related Rules
- [vue/no-unused-emit-declarations](./no-unused-emit-declarations.md)
+- [vue/require-explicit-slots](./require-explicit-slots.md)
## :books: Further Reading
diff --git a/docs/rules/require-explicit-slots.md b/docs/rules/require-explicit-slots.md
new file mode 100644
index 000000000..ac3050896
--- /dev/null
+++ b/docs/rules/require-explicit-slots.md
@@ -0,0 +1,68 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/require-explicit-slots
+description: require slots to be explicitly defined with defineSlots
+---
+
+# vue/require-explicit-slots
+
+> require slots to be explicitly defined
+
+- :exclamation: ***This rule has not been released yet.***
+
+## :book: Rule Details
+
+This rule enforces all slots used in the template to be defined once either in the `script setup` block with the [`defineSlots`](https://vuejs.org/api/sfc-script-setup.html) macro, or with the [`slots property`](https://vuejs.org/api/options-rendering.html#slots) in the Options API.
+
+
+
+```vue
+
+
+
+
+
+
+
+
+
+
+```
+
+
+
+
+
+```vue
+
+
+
+
+
+
+
+
+
+
+```
+
+
+
+## :wrench: Options
+
+Nothing.
diff --git a/lib/rules/require-explicit-slots.js b/lib/rules/require-explicit-slots.js
new file mode 100644
index 000000000..ed2d2edb9
--- /dev/null
+++ b/lib/rules/require-explicit-slots.js
@@ -0,0 +1,128 @@
+/**
+ * @author Mussin Benarbia
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+const utils = require('../utils')
+
+/**
+ * @typedef {import('@typescript-eslint/types').TSESTree.TypeNode} TypeNode
+ */
+
+module.exports = {
+ meta: {
+ type: 'problem',
+ docs: {
+ description: 'require slots to be explicitly defined',
+ categories: undefined,
+ url: 'https://eslint.vuejs.org/rules/require-explicit-slots.html'
+ },
+ fixable: null,
+ schema: [],
+ messages: {
+ requireExplicitSlots: 'Slots must be explicitly defined.',
+ alreadyDefinedSlot: 'Slot {{slotName}} is already defined.'
+ }
+ },
+ /** @param {RuleContext} context */
+ create(context) {
+ const sourceCode = context.getSourceCode()
+ const documentFragment =
+ sourceCode.parserServices.getDocumentFragment &&
+ sourceCode.parserServices.getDocumentFragment()
+ if (!documentFragment) {
+ return {}
+ }
+ const scripts = documentFragment.children.filter(
+ (element) => utils.isVElement(element) && element.name === 'script'
+ )
+ if (scripts.every((script) => !utils.hasAttribute(script, 'lang', 'ts'))) {
+ return {}
+ }
+ const slotsDefined = new Set()
+
+ return utils.compositingVisitors(
+ utils.defineScriptSetupVisitor(context, {
+ onDefineSlotsEnter(node) {
+ const typeArguments =
+ 'typeArguments' in node ? node.typeArguments : node.typeParameters
+ const param = /** @type {TypeNode|undefined} */ (
+ typeArguments?.params[0]
+ )
+ if (!param) return
+
+ if (param.type === 'TSTypeLiteral') {
+ for (const memberNode of param.members) {
+ const slotName = memberNode.key.name
+ if (slotsDefined.has(slotName)) {
+ context.report({
+ node: memberNode,
+ messageId: 'alreadyDefinedSlot',
+ data: {
+ slotName
+ }
+ })
+ } else {
+ slotsDefined.add(slotName)
+ }
+ }
+ }
+ }
+ }),
+ utils.executeOnVue(context, (obj) => {
+ const slotsProperty = utils.findProperty(obj, 'slots')
+ if (!slotsProperty) return
+
+ const slotsTypeHelper =
+ slotsProperty.value.typeAnnotation?.typeName.name === 'SlotsType' &&
+ slotsProperty.value.typeAnnotation
+ if (!slotsTypeHelper) return
+
+ const typeArguments =
+ 'typeArguments' in slotsTypeHelper
+ ? slotsTypeHelper.typeArguments
+ : slotsTypeHelper.typeParameters
+ const param = /** @type {TypeNode|undefined} */ (
+ typeArguments?.params[0]
+ )
+ if (!param) return
+
+ if (param.type === 'TSTypeLiteral') {
+ for (const memberNode of param.members) {
+ const slotName = memberNode.key.name
+ if (slotsDefined.has(slotName)) {
+ context.report({
+ node: memberNode,
+ messageId: 'alreadyDefinedSlot',
+ data: {
+ slotName
+ }
+ })
+ } else {
+ slotsDefined.add(slotName)
+ }
+ }
+ }
+ }),
+ utils.defineTemplateBodyVisitor(context, {
+ "VElement[name='slot']"(node) {
+ let slotName = 'default'
+
+ const slotNameAttr = utils.getAttribute(node, 'name')
+
+ if (slotNameAttr) {
+ slotName = slotNameAttr.value.value
+ }
+
+ if (!slotsDefined.has(slotName)) {
+ context.report({
+ node,
+ messageId: 'requireExplicitSlots'
+ })
+ }
+ }
+ })
+ )
+ }
+}
diff --git a/tests/lib/rules/require-explicit-slots.js b/tests/lib/rules/require-explicit-slots.js
new file mode 100644
index 000000000..1849ea9a1
--- /dev/null
+++ b/tests/lib/rules/require-explicit-slots.js
@@ -0,0 +1,269 @@
+/**
+ * @author Mussin Benarbia
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+const RuleTester = require('eslint').RuleTester
+const rule = require('../../../lib/rules/require-explicit-slots')
+
+const tester = new RuleTester({
+ parser: require.resolve('vue-eslint-parser'),
+ parserOptions: {
+ parser: require.resolve('@typescript-eslint/parser'),
+ ecmaVersion: 2020,
+ sourceType: 'module'
+ }
+})
+
+tester.run('require-explicit-slots', rule, {
+ valid: [
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+
+
+ `
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+
+
+ `
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+
+
+ `
+ },
+
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+
+
+ `
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+
+
+ `
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+
+
+ `
+ },
+ // does not report any error if the script is not TS
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+
+
+ `,
+ parserOptions: {
+ parser: null
+ }
+ }
+ ],
+ invalid: [
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+
+
+ `,
+ errors: [
+ {
+ message: 'Slots must be explicitly defined.'
+ }
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+
+
+ `,
+ errors: [
+ {
+ message: 'Slots must be explicitly defined.'
+ }
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+
+
+ `,
+ errors: [
+ {
+ message: 'Slots must be explicitly defined.'
+ }
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+
+
+ `,
+ errors: [
+ {
+ message: 'Slots must be explicitly defined.'
+ }
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+
+
+ `,
+ errors: [
+ {
+ message: 'Slots must be explicitly defined.'
+ }
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+
+
+ `,
+ errors: [
+ {
+ message: 'Slot foo is already defined.'
+ }
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+
+
+ `,
+ errors: [
+ {
+ message: 'Slot foo is already defined.'
+ }
+ ]
+ }
+ ]
+})