This is an automated email from the ASF dual-hosted git repository.

rusackas pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git


The following commit(s) were added to refs/heads/master by this push:
     new c5131bff47e chore(a11y): enable 9 more jsx-a11y oxlint rules as errors 
(#42006)
c5131bff47e is described below

commit c5131bff47eefdee0b92016ffe4afa944cee0e95
Author: Evan Rusackas <[email protected]>
AuthorDate: Mon Jul 13 11:39:13 2026 -0700

    chore(a11y): enable 9 more jsx-a11y oxlint rules as errors (#42006)
    
    Co-authored-by: Claude Opus 4.8 <[email protected]>
---
 superset-frontend/oxlint.json                      | 28 ++++++++++++++++++----
 .../src/components/FaveStar/index.tsx              |  1 +
 .../src/components/Modal/Modal.tsx                 |  2 ++
 .../src/components/RefreshLabel/index.tsx          |  1 +
 .../src/DeckGLContainer.test.tsx                   |  1 +
 .../TableExploreTree/TreeNodeRenderer.tsx          |  2 --
 .../Chart/DrillDetail/DrillDetailTableControls.tsx |  1 +
 .../ScopingModal/ChartsScopingListPanel.tsx        |  1 +
 .../nativeFilters/FilterBar/Vertical.tsx           |  1 +
 .../FiltersConfigModal/FilterTitleContainer.tsx    |  1 +
 .../FiltersConfigForm/DependencyList.tsx           |  2 +-
 .../FiltersConfigModal/ItemTitleContainer.tsx      |  1 +
 .../components/DataTableControls.tsx               |  1 +
 .../components/ExploreViewContainer/index.tsx      |  1 +
 .../DndColumnSelectPopoverTitle.tsx                |  1 +
 .../controls/DndColumnSelectControl/Option.tsx     |  1 +
 .../MetricControl/AdhocMetricEditPopoverTitle.tsx  |  6 ++++-
 .../components/controls/OptionControls/index.tsx   |  1 +
 .../src/features/queries/QueryPreviewModal.tsx     |  2 ++
 19 files changed, 46 insertions(+), 9 deletions(-)

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 <table>
+      // 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 ? (
         <Icons.StarFilled
diff --git 
a/superset-frontend/packages/superset-ui-core/src/components/Modal/Modal.tsx 
b/superset-frontend/packages/superset-ui-core/src/components/Modal/Modal.tsx
index f8768f29c35..6c00d8884a5 100644
--- a/superset-frontend/packages/superset-ui-core/src/components/Modal/Modal.tsx
+++ b/superset-frontend/packages/superset-ui-core/src/components/Modal/Modal.tsx
@@ -344,6 +344,8 @@ const CustomModal = ({
         className="draggable-trigger"
         onMouseOver={() => dragDisabled && setDragDisabled(false)}
         onMouseOut={() => !dragDisabled && setDragDisabled(true)}
+        onFocus={() => dragDisabled && setDragDisabled(false)}
+        onBlur={() => !dragDisabled && setDragDisabled(true)}
       >
         {title}
       </div>
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 = ({
     <Icons.SyncOutlined
       iconSize="l"
       role="button"
+      tabIndex={disabled ? -1 : 0}
       onClick={disabled ? undefined : onClick}
       css={(theme: SupersetTheme) => ({
         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', () => ({
     <div data-test="maplibre-map" data-map-style={JSON.stringify(mapStyle)}>
       <button
         type="button"
+        aria-label="move map"
         data-test="maplibre-move"
         onClick={() =>
           onMove({ viewState: { longitude: 1, latitude: 2, zoom: 3 } })
diff --git 
a/superset-frontend/src/SqlLab/components/TableExploreTree/TreeNodeRenderer.tsx 
b/superset-frontend/src/SqlLab/components/TableExploreTree/TreeNodeRenderer.tsx
index 5e0eb6c60d2..dc3e83eaefe 100644
--- 
a/superset-frontend/src/SqlLab/components/TableExploreTree/TreeNodeRenderer.tsx
+++ 
b/superset-frontend/src/SqlLab/components/TableExploreTree/TreeNodeRenderer.tsx
@@ -238,7 +238,6 @@ const TreeNodeRenderer: React.FC<TreeNodeRendererProps> = ({
       {identifier === 'schema' && (
         <div
           className="side-action-container"
-          role="menu"
           onClick={e => e.stopPropagation()}
         >
           {pinnedSchemas.has(schema) && (
@@ -306,7 +305,6 @@ const TreeNodeRenderer: React.FC<TreeNodeRendererProps> = ({
           return (
             <div
               className="side-action-container"
-              role="menu"
               onClick={e => e.stopPropagation()}
             >
               {isPinned && (
diff --git 
a/superset-frontend/src/components/Chart/DrillDetail/DrillDetailTableControls.tsx
 
b/superset-frontend/src/components/Chart/DrillDetail/DrillDetailTableControls.tsx
index 17de19f8b79..929f863c640 100644
--- 
a/superset-frontend/src/components/Chart/DrillDetail/DrillDetailTableControls.tsx
+++ 
b/superset-frontend/src/components/Chart/DrillDetail/DrillDetailTableControls.tsx
@@ -163,6 +163,7 @@ export default function TableControls({
             iconSize="l"
             aria-label={t('Reload')}
             role="button"
+            tabIndex={0}
             onClick={onReload}
           />
         </Tooltip>
diff --git 
a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/CrossFilters/ScopingModal/ChartsScopingListPanel.tsx
 
b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/CrossFilters/ScopingModal/ChartsScopingListPanel.tsx
index 9775de8022e..05a34f8a0ee 100644
--- 
a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/CrossFilters/ScopingModal/ChartsScopingListPanel.tsx
+++ 
b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/CrossFilters/ScopingModal/ChartsScopingListPanel.tsx
@@ -142,6 +142,7 @@ export const ChartsScopingListPanel = ({
       </AddButtonContainer>
       <FilterTitle
         role="button"
+        tabIndex={0}
         onClick={() => setCurrentChartId(undefined)}
         className={activeChartId === undefined ? 'active' : ''}
       >
diff --git 
a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/Vertical.tsx
 
b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/Vertical.tsx
index f133db24a25..17d457f81d5 100644
--- 
a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/Vertical.tsx
+++ 
b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/Vertical.tsx
@@ -266,6 +266,7 @@ const VerticalFilterBar: FC<VerticalBarProps> = ({
           className={cx({ open: !filtersOpen })}
           onClick={openFiltersBar}
           role="button"
+          tabIndex={0}
           offset={offset}
         >
           <Icons.VerticalAlignTopOutlined
diff --git 
a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FilterTitleContainer.tsx
 
b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FilterTitleContainer.tsx
index 19d7427917b..e5420ad1b63 100644
--- 
a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FilterTitleContainer.tsx
+++ 
b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FilterTitleContainer.tsx
@@ -166,6 +166,7 @@ const FilterTitleContainer = forwardRef<HTMLDivElement, 
Props>(
         <FilterTitle
           role="tab"
           key={`filter-title-tab-${id}`}
+          tabIndex={0}
           onClick={() => onChange(id)}
           className={classNames.join(' ')}
           aria-selected={isActive}
diff --git 
a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/DependencyList.tsx
 
b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/DependencyList.tsx
index 072ba9ba87f..52c6952e062 100644
--- 
a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/DependencyList.tsx
+++ 
b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/DependencyList.tsx
@@ -175,7 +175,7 @@ const List = ({
         />
       ))}
       {availableFilters.length > rows.length && (
-        <AddFilter role="button" onClick={onAdd}>
+        <AddFilter role="button" tabIndex={0} onClick={onAdd}>
           <Icons.PlusOutlined iconSize="xs" />
           {t('Add filter')}
         </AddFilter>
diff --git 
a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/ItemTitleContainer.tsx
 
b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/ItemTitleContainer.tsx
index 78b9186dca6..388421b5ac1 100644
--- 
a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/ItemTitleContainer.tsx
+++ 
b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/ItemTitleContainer.tsx
@@ -139,6 +139,7 @@ const ItemTitleContainer = forwardRef<HTMLDivElement, 
Props>(
         <ItemTitle
           role="tab"
           key={`item-title-tab-${id}`}
+          tabIndex={0}
           onClick={() => onChange(id)}
           className={classNames.join(' ')}
           aria-selected={isActive}
diff --git 
a/superset-frontend/src/explore/components/DataTablesPane/components/DataTableControls.tsx
 
b/superset-frontend/src/explore/components/DataTablesPane/components/DataTableControls.tsx
index 8ca46c6f136..abae9473796 100644
--- 
a/superset-frontend/src/explore/components/DataTablesPane/components/DataTableControls.tsx
+++ 
b/superset-frontend/src/explore/components/DataTablesPane/components/DataTableControls.tsx
@@ -140,6 +140,7 @@ export const TableControls = ({
               iconSize="l"
               aria-label={t('Reload')}
               role="button"
+              tabIndex={0}
               onClick={onReload}
             />
           </Tooltip>
diff --git 
a/superset-frontend/src/explore/components/ExploreViewContainer/index.tsx 
b/superset-frontend/src/explore/components/ExploreViewContainer/index.tsx
index faaacd102f5..aebaa64da17 100644
--- a/superset-frontend/src/explore/components/ExploreViewContainer/index.tsx
+++ b/superset-frontend/src/explore/components/ExploreViewContainer/index.tsx
@@ -1026,6 +1026,7 @@ function ExploreViewContainer(props: 
ExploreViewContainerProps) {
             data-test="open-datasource-tab"
             role="button"
             tabIndex={0}
+            aria-label={t('Open Datasource tab')}
           >
             <span role="button" tabIndex={0} className="action-button">
               <Tooltip title={t('Open Datasource tab')}>
diff --git 
a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndColumnSelectPopoverTitle.tsx
 
b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndColumnSelectPopoverTitle.tsx
index dcc01245432..05c3cd3744c 100644
--- 
a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndColumnSelectPopoverTitle.tsx
+++ 
b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndColumnSelectPopoverTitle.tsx
@@ -94,6 +94,7 @@ export const DndColumnSelectPopoverTitle = ({
         data-test="AdhocMetricEditTitle#trigger"
         onMouseOver={onMouseOver}
         onMouseOut={onMouseOut}
+        onFocus={onMouseOver}
         onClick={onClick}
         onBlur={onBlur}
         role="button"
diff --git 
a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/Option.tsx
 
b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/Option.tsx
index 6ab8ef322d4..2f8b733960e 100644
--- 
a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/Option.tsx
+++ 
b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/Option.tsx
@@ -58,6 +58,7 @@ export default function Option({
             text-align: center;
           `}
           role="button"
+          tabIndex={0}
           data-test="remove-control-button"
           onClick={onClickClose}
         >
diff --git 
a/superset-frontend/src/explore/components/controls/MetricControl/AdhocMetricEditPopoverTitle.tsx
 
b/superset-frontend/src/explore/components/controls/MetricControl/AdhocMetricEditPopoverTitle.tsx
index 2e52b23ebee..57d5ed8e3c2 100644
--- 
a/superset-frontend/src/explore/components/controls/MetricControl/AdhocMetricEditPopoverTitle.tsx
+++ 
b/superset-frontend/src/explore/components/controls/MetricControl/AdhocMetricEditPopoverTitle.tsx
@@ -64,7 +64,10 @@ const AdhocMetricEditPopoverTitle: 
FC<AdhocMetricEditPopoverTitleProps> = ({
   const handleMouseOver = useCallback(() => setIsHovered(true), []);
   const handleMouseOut = useCallback(() => setIsHovered(false), []);
   const handleClick = useCallback(() => setIsEditMode(true), []);
-  const handleBlur = useCallback(() => setIsEditMode(false), []);
+  const handleBlur = useCallback(() => {
+    setIsHovered(false);
+    setIsEditMode(false);
+  }, []);
 
   const handleKeyPress = useCallback(
     (ev: KeyboardEvent<HTMLInputElement>) => {
@@ -115,6 +118,7 @@ const AdhocMetricEditPopoverTitle: 
FC<AdhocMetricEditPopoverTitleProps> = ({
         data-test="AdhocMetricEditTitle#trigger"
         onMouseOver={handleMouseOver}
         onMouseOut={handleMouseOut}
+        onFocus={handleMouseOver}
         onClick={handleClick}
         onBlur={handleBlur}
         role="button"
diff --git 
a/superset-frontend/src/explore/components/controls/OptionControls/index.tsx 
b/superset-frontend/src/explore/components/controls/OptionControls/index.tsx
index 97c414c37bd..a8f65bd0ddb 100644
--- a/superset-frontend/src/explore/components/controls/OptionControls/index.tsx
+++ b/superset-frontend/src/explore/components/controls/OptionControls/index.tsx
@@ -350,6 +350,7 @@ export const OptionControlLabel = ({
         role="button"
         data-test="remove-control-button"
         onClick={onRemove}
+        tabIndex={0}
       >
         <Icons.CloseOutlined
           iconSize="m"
diff --git a/superset-frontend/src/features/queries/QueryPreviewModal.tsx 
b/superset-frontend/src/features/queries/QueryPreviewModal.tsx
index 823aae806c5..1001478ae57 100644
--- a/superset-frontend/src/features/queries/QueryPreviewModal.tsx
+++ b/superset-frontend/src/features/queries/QueryPreviewModal.tsx
@@ -148,6 +148,7 @@ function QueryPreviewModal({
         <QueryViewToggle>
           <TabButton
             role="button"
+            tabIndex={0}
             data-test="toggle-user-sql"
             className={cx({ active: currentTab === 'user' })}
             onClick={() => setCurrentTab('user')}
@@ -156,6 +157,7 @@ function QueryPreviewModal({
           </TabButton>
           <TabButton
             role="button"
+            tabIndex={0}
             data-test="toggle-executed-sql"
             className={cx({ active: currentTab === 'executed' })}
             onClick={() => setCurrentTab('executed')}

Reply via email to