Skip to content

fix: a11y bug aria-expanded not supported here [AC-4117]#1330

Open
Stefan3002 wants to merge 1 commit intocanonical:mainfrom
Stefan3002:fix(a11y)-fix-a11y-search-and-filter-violation
Open

fix: a11y bug aria-expanded not supported here [AC-4117]#1330
Stefan3002 wants to merge 1 commit intocanonical:mainfrom
Stefan3002:fix(a11y)-fix-a11y-search-and-filter-violation

Conversation

@Stefan3002
Copy link
Contributor

@Stefan3002 Stefan3002 commented Mar 16, 2026

Done

  • Fixed a11y error highlighted in this screenshot:
image
  • The aria-expanded attribute is not allowed on a div.
  • Following a MM discussion we think it is best to remove aria-expanded because in this case it doesn't really make sense:
    • Firstly, we can't put it on the outer div, but we can't make that div a button or anything else that supports the attribute. We have to make a sacrifice somehow.
    • Secondly, the +N overflow component is a span with a role of a button and it would make sense to have it use th aria-expanded attr, but when you keyboard navigate the component, you will see the +N overflow component disappears making space for the actual overflowing chips, so it adds no value to keyboard navigation hence we believe it is best to remove aria-expanded.
  • The big problem is that vanilla uses the aria-expanded attribute to determine when to expand the search bar, but it was wrongly used on the search bar for historical reason. This is why, this PR is closely related to this vanilla PR.
  • The a11y error is now gone

QA

Pinging @canonical/react-library-maintainers for a review.

We can run axe-core quickly like so: npx @axe-core/cli http://localhost:5173/ --verbose
I used the following App.tsx file to test:

import React, { useMemo, useState } from "react";
import { SearchAndFilter } from "./index";
import type { SearchAndFilterChip } from "./components/SearchAndFilter/types";

const App = (): React.JSX.Element => {
  const [returnedData, setReturnedData] = useState<SearchAndFilterChip[]>([]);

  // Seed a bunch of chips so the input overflows and shows the "+N" expander.
  const existingSearchData = useMemo<SearchAndFilterChip[]>(
    () => [
      { value: "owner:me", quoteValue: false },
      { value: "status:open", quoteValue: false },
      { value: "tag:frontend", quoteValue: false },
      { value: "tag:ui", quoteValue: false },
      { value: "tag:accessibility", quoteValue: false },
      { value: "priority:high", quoteValue: false },
      { value: "env:prod", quoteValue: false },
      { value: "region:eu", quoteValue: false },
      { value: "type:bug", quoteValue: false },
      { value: "team:design-systems", quoteValue: false },
      { value: "component:search", quoteValue: false },
      { value: "component:filter", quoteValue: false },
    ],
    [],
  );

  const filterPanelData = useMemo(
    () => [
      {
        id: 1,
        heading: "Status",
        chips: [
          { value: "status:open", quoteValue: false },
          { value: "status:closed", quoteValue: false },
          { value: "status:review", quoteValue: false },
        ],
      },
      {
        id: 2,
        heading: "Priority",
        chips: [
          { value: "priority:low", quoteValue: false },
          { value: "priority:medium", quoteValue: false },
          { value: "priority:high", quoteValue: false },
        ],
      },
      {
        id: 3,
        heading: "Tags",
        chips: [
          { value: "tag:frontend", quoteValue: false },
          { value: "tag:backend", quoteValue: false },
          { value: "tag:accessibility", quoteValue: false },
          { value: "tag:performance", quoteValue: false },
          { value: "tag:ui", quoteValue: false },
        ],
      },
    ],
    [],
  );

  return (
    <div style={{ padding: 24, maxWidth: 720 }}>
      <h1 style={{ marginTop: 0 }}>Search and filter demo</h1>
      <p style={{ marginTop: 0, opacity: 0.85 }}>
        The control will overflow with the seeded chips. Click the{" "}
        <code>+N</code> indicator to expand and see all chips.
      </p>

      <SearchAndFilter
        existingSearchData={existingSearchData}
        filterPanelData={filterPanelData}
        returnSearchData={setReturnedData}
      />

      <div style={{ marginTop: 16 }}>
        <h2 style={{ marginBottom: 8 }}>Returned search data</h2>
        <pre style={{ margin: 0, padding: 12, background: "rgba(0,0,0,0.15)" }}>
          {JSON.stringify(returnedData, null, 2)}
        </pre>
      </div>
    </div>
  );
};

export default App;

REFS:

  • MDN says: "The aria-expanded attribute is applied to a focusable, interactive control that toggles the visibility of another element."
  • Example from the same MDN link:
<button aria-expanded="false" aria-controls="menu">
  Menu
</button>
  • w3 here says: "When the combobox popup is not visible, the element with role combobox has aria-expanded set to false. When the popup element is visible, aria-expanded is set to true." In our case the "element with role combobox " is the one that triggers the panel to open i.e: the input tag
  • MDN here says: "The aria-expanded attribute is applied to a focusable, interactive control that toggles the visibility of another element."
  • w3 here says:

"Required States and Properties:
aria-controls
aria-expanded" for combobox

Fixes

Fixes: #AC-4117.

@webteam-app
Copy link

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes accessibility violations in the SearchAndFilter UI by moving aria-expanded off non-interactive containers and wiring the trigger element to the panel it controls.

Changes:

  • Move aria-expanded from a container <div> onto the search <input> and add aria-controls + an id on the controlled panel.
  • Update click handling so clicking the search/filter area expands the internal “expanded” state.
  • Remove aria-expanded from the filter panel chips wrapper and drop useEffectEvent usage in FilterPanelSection.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 6 comments.

File Description
src/components/SearchAndFilter/SearchAndFilter.tsx Moves ARIA expanded/controls onto the input and links it to the panel; adjusts click behavior.
src/components/SearchAndFilter/FilterPanelSection/FilterPanelSection.tsx Removes invalid aria-expanded usage on a non-interactive div; refactors overflow recalculation callback.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@Stefan3002 Stefan3002 changed the title a11y bug: aria-expanded not supported here [AC-4117] fix: a11y bug aria-expanded not supported here [AC-4117] Mar 18, 2026
@Stefan3002 Stefan3002 force-pushed the fix(a11y)-fix-a11y-search-and-filter-violation branch 2 times, most recently from 62e1bf1 to 6d16c55 Compare March 18, 2026 08:43
@Stefan3002 Stefan3002 requested a review from Copilot March 18, 2026 08:45
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes an accessibility validation issue in SearchAndFilter by relocating aria-expanded from non-interactive containers to a more appropriate control and linking the trigger to the panel via aria-controls.

Changes:

  • Move aria-expanded from a <div> onto the <input> and add role="combobox" plus aria-controls.
  • Add a stable id on the filter panel container for aria-controls targeting.
  • Remove aria-expanded from a <div> in FilterPanelSection and replace useEffectEvent usage.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

File Description
src/components/SearchAndFilter/SearchAndFilter.tsx Moves ARIA state to the input, adds aria-controls, and assigns an id to the panel.
src/components/SearchAndFilter/FilterPanelSection/FilterPanelSection.tsx Removes invalid aria-expanded usage and replaces useEffectEvent with a local function.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@Stefan3002 Stefan3002 force-pushed the fix(a11y)-fix-a11y-search-and-filter-violation branch 2 times, most recently from 29f48cb to 3063e18 Compare March 18, 2026 09:20
@Stefan3002 Stefan3002 requested a review from Copilot March 18, 2026 09:22
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR addresses an accessibility rule violation in the SearchAndFilter control by moving aria-expanded off non-interactive containers and linking the element that toggles the panel to the panel via ARIA attributes.

Changes:

  • Move aria-expanded from a wrapper <div> to the <input> and add aria-controls linkage to the panel.
  • Add a fixed id to the panel to support aria-controls.
  • Remove aria-expanded from the FilterPanelSection chips wrapper <div>.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 6 comments.

File Description
src/components/SearchAndFilter/SearchAndFilter.tsx Moves expansion semantics to the input (adds role="combobox", aria-expanded, aria-controls) and adds an id to the panel.
src/components/SearchAndFilter/FilterPanelSection/FilterPanelSection.tsx Removes aria-expanded from a non-interactive chips wrapper element.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@Stefan3002 Stefan3002 marked this pull request as draft March 18, 2026 10:31
@Stefan3002 Stefan3002 force-pushed the fix(a11y)-fix-a11y-search-and-filter-violation branch from 3063e18 to c8b530c Compare March 19, 2026 09:38
@Stefan3002 Stefan3002 force-pushed the fix(a11y)-fix-a11y-search-and-filter-violation branch from c8b530c to 007cb88 Compare March 19, 2026 10:05
@Stefan3002 Stefan3002 requested a review from Copilot March 19, 2026 10:17
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR addresses an accessibility validation issue in the SearchAndFilter components by removing aria-expanded from non-interactive containers and updating related UI/test expectations.

Changes:

  • Replace aria-expanded on the SearchAndFilter search container with data-expanded.
  • Remove aria-expanded from the FilterPanelSection chips wrapper and adjust tests/snapshots accordingly.
  • Improve Chip dismiss button accessibility by adding an aria-label and marking the icon as aria-hidden.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/components/SearchAndFilter/SearchAndFilter.tsx Replaces aria-expanded on a <div> with data-expanded.
src/components/SearchAndFilter/__snapshots__/SearchAndFilter.test.tsx.snap Updates snapshot to reflect data-expanded instead of aria-expanded.
src/components/SearchAndFilter/FilterPanelSection/FilterPanelSection.tsx Removes aria-expanded from the chips wrapper <div>.
src/components/SearchAndFilter/FilterPanelSection/FilterPanelSection.test.tsx Updates the overflow/expansion test expectations and setup.
src/components/SearchAndFilter/FilterPanelSection/__snapshots__/FilterPanelSection.test.tsx.snap Updates snapshot to remove aria-expanded.
src/components/Chip/Chip.tsx Adds aria-label to dismiss button and hides decorative icon from AT.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@Stefan3002 Stefan3002 force-pushed the fix(a11y)-fix-a11y-search-and-filter-violation branch from 007cb88 to b80be8c Compare March 19, 2026 10:52
@Stefan3002 Stefan3002 force-pushed the fix(a11y)-fix-a11y-search-and-filter-violation branch from b80be8c to 4352a26 Compare March 19, 2026 11:01
@Stefan3002 Stefan3002 requested a review from Copilot March 19, 2026 11:01
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR addresses an accessibility validator issue in the SearchAndFilter UI by removing/avoiding invalid aria-expanded usage on non-interactive elements and updating related component tests and snapshots.

Changes:

  • Replaced aria-expanded on the SearchAndFilter container div with a non-ARIA state attribute (data-expanded) and removed aria-expanded from the FilterPanelSection chips wrapper div.
  • Improved Chip dismiss button accessibility by adding an aria-label and marking the close icon as aria-hidden.
  • Updated tests and snapshots to reflect the new attributes and accessible names, including adding an overflow/counter interaction test for FilterPanelSection.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/components/SearchAndFilter/SearchAndFilter.tsx Replaces aria-expanded with data-expanded on the search container.
src/components/SearchAndFilter/SearchAndFilter.test.tsx Updates assertions around expansion and updates dismiss button accessible name usage.
src/components/SearchAndFilter/snapshots/SearchAndFilter.test.tsx.snap Snapshot updated for data-expanded instead of aria-expanded.
src/components/SearchAndFilter/FilterPanelSection/FilterPanelSection.tsx Removes aria-expanded from chips wrapper.
src/components/SearchAndFilter/FilterPanelSection/FilterPanelSection.test.tsx Adds/updates overflow counter interaction testing.
src/components/SearchAndFilter/FilterPanelSection/snapshots/FilterPanelSection.test.tsx.snap Snapshot updated after removing aria-expanded.
src/components/Chip/Chip.tsx Adds aria-label to dismiss button and hides icon from AT.
src/components/Chip/Chip.test.tsx Updates expectations for the dismiss button accessible name.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +108 to +117
// Jest is unaware of layout so we must mock the offsetTop and offsetHeight
// of the chips to force the overflow counter to show.
Object.defineProperty(HTMLElement.prototype, "offsetHeight", {
configurable: true,
value: 40,
});
Object.defineProperty(HTMLElement.prototype, "offsetTop", {
configurable: true,
value: 100,
});
aria-expanded={expanded}
ref={chipWrapper}
>
<div className="p-filter-panel-section__chips" ref={chipWrapper}>
@Stefan3002 Stefan3002 marked this pull request as ready for review March 19, 2026 11:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants