diff --git a/superset-frontend/oxlint.json b/superset-frontend/oxlint.json
index 27563d81c5e..1516d96689c 100644
--- a/superset-frontend/oxlint.json
+++ b/superset-frontend/oxlint.json
@@ -52,10 +52,6 @@
// require-default-props, sort-comp, static-property-placement
// (prefer-stateless-function / function-component-definition
// are represented by react/prefer-function-component below)
- // jsx-a11y: interactive-supports-focus,
- // no-interactive-element-to-noninteractive-role,
- // no-noninteractive-element-interactions,
- // no-noninteractive-element-to-interactive-role
// typescript: naming-convention
// unicorn: prevent-abbreviations
@@ -196,6 +192,7 @@
// === JSX-a11y rules ===
"jsx-a11y/alt-text": "error",
+ "jsx-a11y/anchor-ambiguous-text": "error",
"jsx-a11y/anchor-has-content": "error",
"jsx-a11y/anchor-is-valid": "error",
"jsx-a11y/aria-activedescendant-has-tabindex": "error",
@@ -203,20 +200,31 @@
"jsx-a11y/aria-proptypes": "error",
"jsx-a11y/aria-role": ["error", { "ignoreNonDOM": false }],
"jsx-a11y/aria-unsupported-elements": "error",
+ "jsx-a11y/autocomplete-valid": "error",
"jsx-a11y/click-events-have-key-events": "off",
+ "jsx-a11y/control-has-associated-label": "error",
"jsx-a11y/heading-has-content": "error",
"jsx-a11y/html-has-lang": "error",
"jsx-a11y/iframe-has-title": "error",
"jsx-a11y/img-redundant-alt": "error",
+ "jsx-a11y/interactive-supports-focus": "error",
"jsx-a11y/label-has-associated-control": "error",
"jsx-a11y/lang": "error",
"jsx-a11y/media-has-caption": "error",
- "jsx-a11y/mouse-events-have-key-events": "off",
+ "jsx-a11y/mouse-events-have-key-events": "error",
"jsx-a11y/no-access-key": "error",
+ "jsx-a11y/no-aria-hidden-on-focusable": "error",
"jsx-a11y/no-autofocus": ["error", { "ignoreNonDOM": true }],
"jsx-a11y/no-distracting-elements": "error",
+ "jsx-a11y/no-interactive-element-to-noninteractive-role": "error",
+ "jsx-a11y/no-noninteractive-element-interactions": "error",
+ "jsx-a11y/no-noninteractive-element-to-interactive-role": "error",
"jsx-a11y/no-noninteractive-tabindex": "error",
"jsx-a11y/no-redundant-roles": "error",
+ // TODO: Graduate to "error" — remaining violations are event-boundary
+ // wrappers (divs/spans whose handlers only stopPropagation) and test
+ // scaffolding where adding a role is semantically wrong. Needs a
+ // dedicated pass; see PR enabling the other jsx-a11y interactive rules.
"jsx-a11y/no-static-element-interactions": "off",
"jsx-a11y/role-has-required-aria-props": "error",
"jsx-a11y/role-supports-aria-props": "error",
@@ -289,6 +297,16 @@
"rules": {
"jsx-a11y/no-redundant-roles": "off"
}
+ },
+ {
+ // The pivot table intentionally exposes `role="grid"` on its
+ // for interactive grid semantics (asserted by tableRenders.test.tsx).
+ "files": [
+ "plugins/plugin-chart-pivot-table/src/react-pivottable/TableRenderers.tsx"
+ ],
+ "rules": {
+ "jsx-a11y/no-noninteractive-element-to-interactive-role": "off"
+ }
}
],
"ignorePatterns": [
diff --git a/superset-frontend/packages/superset-ui-core/src/components/FaveStar/index.tsx b/superset-frontend/packages/superset-ui-core/src/components/FaveStar/index.tsx
index 308e44e4630..fbfb9c5e4a1 100644
--- a/superset-frontend/packages/superset-ui-core/src/components/FaveStar/index.tsx
+++ b/superset-frontend/packages/superset-ui-core/src/components/FaveStar/index.tsx
@@ -60,6 +60,7 @@ export const FaveStar = ({
className="fave-unfave-icon"
data-test="fave-unfave-icon"
role="button"
+ tabIndex={0}
>
{isStarred ? (
dragDisabled && setDragDisabled(false)}
onMouseOut={() => !dragDisabled && setDragDisabled(true)}
+ onFocus={() => dragDisabled && setDragDisabled(false)}
+ onBlur={() => !dragDisabled && setDragDisabled(true)}
>
{title}
diff --git a/superset-frontend/packages/superset-ui-core/src/components/RefreshLabel/index.tsx b/superset-frontend/packages/superset-ui-core/src/components/RefreshLabel/index.tsx
index 1c01ca63acd..c5c162a5098 100644
--- a/superset-frontend/packages/superset-ui-core/src/components/RefreshLabel/index.tsx
+++ b/superset-frontend/packages/superset-ui-core/src/components/RefreshLabel/index.tsx
@@ -36,6 +36,7 @@ const RefreshLabel = ({
({
cursor: 'pointer',
diff --git a/superset-frontend/plugins/preset-chart-deckgl/src/DeckGLContainer.test.tsx b/superset-frontend/plugins/preset-chart-deckgl/src/DeckGLContainer.test.tsx
index 21c30ef8a37..b035d2ee87d 100644
--- a/superset-frontend/plugins/preset-chart-deckgl/src/DeckGLContainer.test.tsx
+++ b/superset-frontend/plugins/preset-chart-deckgl/src/DeckGLContainer.test.tsx
@@ -42,6 +42,7 @@ jest.mock('react-map-gl/maplibre', () => ({