diff --git a/packages/eslint-plugin/README.md b/packages/eslint-plugin/README.md
index 608991c2173..476155c4931 100644
--- a/packages/eslint-plugin/README.md
+++ b/packages/eslint-plugin/README.md
@@ -160,8 +160,13 @@ Ensure that appropriate aria-attributes are set for `EuiBetaBadge`, `EuiButtonIc
Ensure `EuiTooltip` components are anchored to elements that can receive keyboard focus, making them accessible to all users. When using non-interactive elements (like `span`or `EuiText`) as tooltip anchors, they must include `tabIndex={0}` to be keyboard-focusable. For better accessibility, prefer using semantic interactive components (like `EuiButton` or `EuiLink`) which are focusable by default.
### `@elastic/eui/accessible-interactive-element`
+
Ensure interactive EUI components (like e.g. `EuiLink`, `EuiButton`, `EuiRadio`) remain accessible by prohibiting `tabIndex={-1}`, which removes them from keyboard navigation.
+### `@elastic/eui/require-table-caption`
+
+Ensure `EuiInMemoryTable`, `EuiBasicTable` have a `tableCaption` property for accessibility.
+
## Testing
### Running unit tests
diff --git a/packages/eslint-plugin/changelogs/upcoming/9168.md b/packages/eslint-plugin/changelogs/upcoming/9168.md
new file mode 100644
index 00000000000..ea476b2c67f
--- /dev/null
+++ b/packages/eslint-plugin/changelogs/upcoming/9168.md
@@ -0,0 +1 @@
+- Added new `require-table-caption` rule.
diff --git a/packages/eslint-plugin/src/index.ts b/packages/eslint-plugin/src/index.ts
index 34ecddb5451..168704d760d 100644
--- a/packages/eslint-plugin/src/index.ts
+++ b/packages/eslint-plugin/src/index.ts
@@ -20,6 +20,7 @@ import { NoUnnamedInteractiveElement } from './rules/a11y/no_unnamed_interactive
import { TooltipFocusableAnchor } from './rules/a11y/tooltip_focusable_anchor';
import { CallOutAnnounceOnMount } from './rules/a11y/callout_announce_on_mount';
import { AccessibleInteractiveElements } from './rules/a11y/accessible_interactive_element';
+import { RequireTableCaption } from './rules/a11y/require_table_caption';
const config = {
rules: {
@@ -35,6 +36,7 @@ const config = {
'no-unnamed-interactive-element': NoUnnamedInteractiveElement,
'tooltip-focusable-anchor': TooltipFocusableAnchor,
'accessible-interactive-element': AccessibleInteractiveElements,
+ 'require-table-caption': RequireTableCaption,
},
configs: {
recommended: {
@@ -52,6 +54,7 @@ const config = {
'@elastic/eui/no-unnamed-interactive-element': 'warn',
'@elastic/eui/tooltip-focusable-anchor': 'warn',
'@elastic/eui/accessible-interactive-element': 'warn',
+ '@elastic/eui/require-table-caption': 'warn',
},
},
},
diff --git a/packages/eslint-plugin/src/rules/a11y/require_table_caption.test.ts b/packages/eslint-plugin/src/rules/a11y/require_table_caption.test.ts
new file mode 100644
index 00000000000..7c3236f71d5
--- /dev/null
+++ b/packages/eslint-plugin/src/rules/a11y/require_table_caption.test.ts
@@ -0,0 +1,80 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import dedent from 'dedent';
+import { RuleTester } from '@typescript-eslint/rule-tester';
+import { RequireTableCaption } from './require_table_caption';
+
+const languageOptions = {
+ parserOptions: {
+ ecmaFeatures: {
+ jsx: true,
+ },
+ },
+};
+
+const ruleTester = new RuleTester();
+
+ruleTester.run('require-table-caption', RequireTableCaption, {
+ valid: [
+ {
+ code: dedent`
+ const MyComponent = () => (
+