diff --git a/docs/rules/no-aria-hidden-on-focusable.md b/docs/rules/no-aria-hidden-on-focusable.md
new file mode 100644
index 00000000..bf2701a3
--- /dev/null
+++ b/docs/rules/no-aria-hidden-on-focusable.md
@@ -0,0 +1,79 @@
+# no-aria-hidden-on-focusbable
+
+Enforce that `aria-hidden="true"` is not set on focusable elements or parent of focusable elements.
+
+`aria-hidden="true"` can be used to hide purely decorative content from screen reader users. An element with `aria-hidden="true"` that can also be reached by keyboard can lead to confusion or unexpected behavior for screen reader users. Avoid using `aria-hidden="true"` on focusable elements.
+
+See more in [WAI-ARIA Use in HTML](https://www.w3.org/TR/using-aria/#fourth).
+
+### ✔ Succeed
+
+```vue
+
+ Press Me
+
+```
+
+```vue
+
+ Submit
+
+```
+
+```vue
+
+ Some text
+
+```
+
+```vue
+
+ Press
+
+```
+
+```vue
+
+
+
+```
+
+```vue
+
+ Some text
+
+```
+
+### ❌ Fail
+
+```vue
+
+ press me
+
+```
+
+```vue
+
+ press me
+
+```
+
+```vue
+
+ press me
+
+```
+
+```vue
+
+
+ press me
+
+
+```
+
+```vue
+
+ Icon
+
+```
diff --git a/docs/rules/no-role-presentation-on-focusable.md b/docs/rules/no-role-presentation-on-focusable.md
new file mode 100644
index 00000000..2d5f0f3b
--- /dev/null
+++ b/docs/rules/no-role-presentation-on-focusable.md
@@ -0,0 +1,79 @@
+# no-role-presentaion-on-focusbable
+
+Enforce that `role="presentation"` is not set on focusable elements or parent of focusbale elements.
+
+`role="presentation` can be used to hide purely decorative content from screen reader users. An element with `role="presentation"` that can also be reached by keyboard can lead to confusion or unexpected behavior for screen reader users. Avoid using `role="presentation"` on focusable elements.
+
+See more in [WAI-ARIA Use in HTML](https://www.w3.org/TR/using-aria/#fourth).
+
+### ✔ Succeed
+
+```vue
+
+ Press Me
+
+```
+
+```vue
+
+ Submit
+
+```
+
+```vue
+
+ Some text
+
+```
+
+```vue
+
+ Press
+
+```
+
+```vue
+
+
+
+```
+
+```vue
+
+ Some text
+
+```
+
+### ❌ Fail
+
+```vue
+
+ press me
+
+```
+
+```vue
+
+ press me
+
+```
+
+```vue
+
+ press me
+
+```
+
+```vue
+
+
+ press me
+
+
+```
+
+```vue
+
+ Icon
+
+```
diff --git a/src/rules/__tests__/no-aria-hidden-on-focusable.test.ts b/src/rules/__tests__/no-aria-hidden-on-focusable.test.ts
new file mode 100644
index 00000000..b22a9f9f
--- /dev/null
+++ b/src/rules/__tests__/no-aria-hidden-on-focusable.test.ts
@@ -0,0 +1,31 @@
+import rule from "../no-aria-hidden-on-focusable";
+import makeRuleTester from "./makeRuleTester";
+
+makeRuleTester("no-presentation-role-or-aria-hidden-on-focusable", rule, {
+ valid: [
+ "Submit ",
+ "
Some text
",
+ "Submit
",
+ "link ",
+ "Press ",
+ ""
+ ],
+ invalid: [
+ {
+ code: "Submit
",
+ errors: [{ messageId: "default" }]
+ },
+ {
+ code: "Submit ",
+ errors: [{ messageId: "default" }]
+ },
+ {
+ code: "Link ",
+ errors: [{ messageId: "default" }]
+ },
+ {
+ code: "Icon ",
+ errors: [{ messageId: "default" }]
+ }
+ ]
+});
diff --git a/src/rules/__tests__/no-role-presentation-on-focusable.test.ts b/src/rules/__tests__/no-role-presentation-on-focusable.test.ts
new file mode 100644
index 00000000..ddf9c548
--- /dev/null
+++ b/src/rules/__tests__/no-role-presentation-on-focusable.test.ts
@@ -0,0 +1,31 @@
+import rule from "../no-role-presentation-on-focusable";
+import makeRuleTester from "./makeRuleTester";
+
+makeRuleTester("no-role-presentation-role-on-focusable", rule, {
+ valid: [
+ "Submit ",
+ "Some text
",
+ "Submit
",
+ "link ",
+ "Press ",
+ ""
+ ],
+ invalid: [
+ {
+ code: "Submit
",
+ errors: [{ messageId: "default" }]
+ },
+ {
+ code: "Submit ",
+ errors: [{ messageId: "default" }]
+ },
+ {
+ code: "Link ",
+ errors: [{ messageId: "default" }]
+ },
+ {
+ code: "Icon ",
+ errors: [{ messageId: "default" }]
+ }
+ ]
+});
diff --git a/src/rules/no-aria-hidden-on-focusable.ts b/src/rules/no-aria-hidden-on-focusable.ts
new file mode 100644
index 00000000..bb05696f
--- /dev/null
+++ b/src/rules/no-aria-hidden-on-focusable.ts
@@ -0,0 +1,37 @@
+import type { Rule } from "eslint";
+
+import {
+ defineTemplateBodyVisitor,
+ getElementAttributeValue,
+ makeDocsURL
+} from "../utils";
+import hasFocusableElements from "../utils/hasFocusableElement";
+
+const rule: Rule.RuleModule = {
+ meta: {
+ type: "problem",
+ docs: {
+ url: makeDocsURL("no-aria-hidden-on-focusable")
+ },
+ messages: {
+ default:
+ "Focusable/Interactive elements must not have an aria-hidden attribute."
+ },
+ schema: []
+ },
+ create(context) {
+ return defineTemplateBodyVisitor(context, {
+ VElement(node) {
+ const hasAriaHidden = getElementAttributeValue(node, "aria-hidden");
+ if (hasAriaHidden && hasFocusableElements(node)) {
+ context.report({
+ node: node as any,
+ messageId: "default"
+ });
+ }
+ }
+ });
+ }
+};
+
+export default rule;
diff --git a/src/rules/no-role-presentation-on-focusable.ts b/src/rules/no-role-presentation-on-focusable.ts
new file mode 100644
index 00000000..06738847
--- /dev/null
+++ b/src/rules/no-role-presentation-on-focusable.ts
@@ -0,0 +1,38 @@
+import type { Rule } from "eslint";
+
+import {
+ defineTemplateBodyVisitor,
+ getElementAttributeValue,
+ makeDocsURL
+} from "../utils";
+import hasFocusableElements from "../utils/hasFocusableElement";
+
+const rule: Rule.RuleModule = {
+ meta: {
+ type: "problem",
+ docs: {
+ url: makeDocsURL("no-role-presentation-on-focusable")
+ },
+ messages: {
+ default:
+ "Focusable/Interactive elements must not have a presentation role attribute."
+ },
+ schema: []
+ },
+ create(context) {
+ return defineTemplateBodyVisitor(context, {
+ VElement(node) {
+ const hasRolePresentation =
+ getElementAttributeValue(node, "role") === "presentation";
+ if (hasRolePresentation && hasFocusableElements(node)) {
+ context.report({
+ node: node as any,
+ messageId: "default"
+ });
+ }
+ }
+ });
+ }
+};
+
+export default rule;
diff --git a/src/utils.ts b/src/utils.ts
index 14fd542a..290b5870 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -9,6 +9,7 @@ export { default as getInteractiveRoles } from "./utils/getInteractiveRoles";
export { default as hasAccessibleChild } from "./utils/hasAccessibleChild";
export { default as hasAriaLabel } from "./utils/hasAriaLabel";
export { default as hasContent } from "./utils/hasContent";
+export { default as hasFocusableElement } from "./utils/hasFocusableElement";
export { default as hasOnDirective } from "./utils/hasOnDirective";
export { default as hasOnDirectives } from "./utils/hasOnDirectives";
export { default as interactiveHandlers } from "./utils/interactiveHandlers.json";
diff --git a/src/utils/hasFocusableElement.ts b/src/utils/hasFocusableElement.ts
new file mode 100644
index 00000000..37d3ab3b
--- /dev/null
+++ b/src/utils/hasFocusableElement.ts
@@ -0,0 +1,21 @@
+import type { AST } from "vue-eslint-parser";
+import getElementAttributeValue from "./getElementAttributeValue";
+import isInteractiveElement from "./isInteractiveElement";
+
+function hasFocusableElements(node: AST.VElement): boolean {
+ const tabindex = getElementAttributeValue(node, "tabindex");
+
+ if (isInteractiveElement(node)) {
+ return tabindex !== "-1";
+ }
+
+ if (tabindex !== null && tabindex !== "-1") {
+ return true;
+ }
+
+ return node.children.some(
+ (child) => child.type === "VElement" && hasFocusableElements(child)
+ );
+}
+
+export default hasFocusableElements;