Skip to content

Improve Select combobox keyboard navigation and accessible names, and scope Panel Escape to open comboboxes#1134

Merged
shubham-kumar1-eightfold merged 3 commits into
mainfrom
shubham/select-combobox-keyboard-aria
Jun 10, 2026
Merged

Improve Select combobox keyboard navigation and accessible names, and scope Panel Escape to open comboboxes#1134
shubham-kumar1-eightfold merged 3 commits into
mainfrom
shubham/select-combobox-keyboard-aria

Conversation

@shubham-kumar1-eightfold

@shubham-kumar1-eightfold shubham-kumar1-eightfold commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

SUMMARY:

Accessibility fixes for the shared Select (combobox) and Panel components, aligning them with the WAI-ARIA APG Combobox and Dialog patterns. These are product-wide and affect every Octuple Select/Panel consumer. The work was driven by an accessibility audit of the "All Filters" panel on the Duplicate Position intake form, and pairs with the consumer-side change in EightfoldAI/vscode#108828.

src/components/Select/Select.tsx

  • Keyboard navigation into options (both filterable and non-filterable). In handleInputKeyDown, ArrowDown now moves keyboard focus from the combobox input into the dropdown's option list for both filterable (list-autocomplete) and select-only comboboxes. Previously the filterable && guard meant only filterable selects could enter their options by keyboard; plain dropdowns trapped focus on the input. The behavior is now gated on the dropdown being open (dropdownVisible) and calls event.preventDefault() so the page does not also scroll.
  • Accessible name preserved. The input's ariaLabel now falls back to selectInputProps?.ariaLabel (ariaLabel ?? selectInputProps?.ariaLabel) instead of being clobbered to undefined. A combobox name supplied via textInputProps is now kept and announced.
  • Valid, unique option ids. Each option id is now derived from the menu id plus the option index (${selectMenuId.current}-option-${index}) instead of option.text + '-' + index. The previous scheme produced invalid and/or duplicate aria-activedescendant IDREFs (e.g. when option text contained spaces or repeated across options); the ids are now stable and unique.
  • Active-descendant lifecycle. aria-activedescendant is cleared when the listbox closes (it would otherwise dangle, pointing at an option that has unmounted) and is scoped to this Select's own options, so navigating one combobox no longer stamps another Select's input.
  • Clear button accessible name. New optional clearButtonAriaLabel prop (default 'Clear selection') gives the clearable button an accessible name.

src/components/Panel/Panel.tsx

  • Scoped, escalating Escape. The default escapeTargetSelector denylist now includes [role="combobox"][aria-expanded="true"]. The Panel's global Escape handler does not close the panel when the Escape target matches this denylist. Scoping the combobox token to aria-expanded="true" produces an "escalating Escape": pressing Escape while a combobox's listbox is open closes only the listbox (the panel stays open); a second Escape (combobox now collapsed, so it no longer matches the denylist) closes the panel. Previously, Escape on a combobox input closed the entire panel in one keystroke.

Tests & snapshot

  • Updated Select.test.tsx option-id assertions (selection, onOptionsChange, aria-activedescendant, and pill tests) to the new menu-scoped id scheme.
  • Regenerated the Select snapshot for the new ids; no Panel snapshot change.
  • Added tests for the clear-button accessible name and for aria-activedescendant being cleared on close and scoped per-Select.

These changes are standards-aligned with the WAI-ARIA APG Combobox and Dialog patterns; the only API addition is the optional, backward-compatible clearButtonAriaLabel prop.

GITHUB ISSUE (Open Source Contributors)

N/A — internal Eightfold accessibility work (see JIRA below).

JIRA TASK (Eightfold Employees Only):

https://eightfoldai.atlassian.net/browse/ENG-167719

CHANGE TYPE:

  • Bugfix Pull Request
  • Feature Pull Request

TEST COVERAGE:

  • Tests for this change already exist

The Select assertions and snapshot are updated in this PR for the new option ids (see “Tests & snapshot” above) — no reviewer action needed to regenerate snapshots.

TEST PLAN:

Combobox keyboard navigation (Select):

  • Open a non-filterable (select-only) Select, then press ArrowDown from the input → focus moves into the option list and the first option becomes active; the page does not scroll.
  • Open a filterable Select and confirm ArrowDown still moves focus into the options (no regression).
  • With the dropdown closed, ArrowDown does not jump focus into a hidden list.

Accessible name (Select):

  • Provide a combobox name via textInputProps.ariaLabel and confirm the combobox input is announced with that name (no longer undefined).

Option ids / active-descendant (Select):

  • Inspect rendered options and confirm each option id is unique and valid, and that aria-activedescendant on the input points at the active option's id while navigating with the keyboard. Verify with options whose text contains spaces and/or duplicate text across options.

Escalating Escape (Panel):

  • Inside a Panel, open a combobox's listbox and press Escape → only the listbox closes; the panel stays open. Press Escape again (combobox now collapsed) → the panel closes.
  • Confirm Escape still closes the panel directly when focus is not on an expanded combobox (no regression to the existing listbox/menu/tooltip/dropdown/tooltip-wrapper behavior).

Cross-repo: validate end-to-end on the "All Filters" panel of the Duplicate Position intake form via EightfoldAI/vscode#108828.

@codesandbox-ci

codesandbox-ci Bot commented Jun 8, 2026

Copy link
Copy Markdown

This pull request is automatically built and testable in CodeSandbox.

To see build info of the built libraries, click here or the icon next to each commit SHA.

@codecov

codecov Bot commented Jun 8, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 84.39%. Comparing base (47ac84c) to head (e833cf3).

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #1134   +/-   ##
=======================================
  Coverage   84.38%   84.39%           
=======================================
  Files        1230     1230           
  Lines       21427    21433    +6     
  Branches     8132     8137    +5     
=======================================
+ Hits        18081    18088    +7     
+ Misses       3260     3259    -1     
  Partials       86       86           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@shubham-kumar1-eightfold shubham-kumar1-eightfold force-pushed the shubham/select-combobox-keyboard-aria branch from 3c0f6df to f466881 Compare June 8, 2026 09:00
@shubham-kumar1-eightfold shubham-kumar1-eightfold changed the title Select combobox keyboard navigation and active-descendant semantics Keyboard navigation and scoped active-descendant for Select comboboxes Jun 8, 2026
@shubham-kumar1-eightfold shubham-kumar1-eightfold force-pushed the shubham/select-combobox-keyboard-aria branch from f466881 to 1866685 Compare June 8, 2026 09:41
… Panel Escape

Select: ArrowDown moves focus into the listbox for both filterable and non-filterable selects; restore the input accessible name from textInputProps; use stable valid option ids for aria-activedescendant. Panel: scope the Escape denylist to open comboboxes so Escape closes an open listbox first, then the panel.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@shubham-kumar1-eightfold shubham-kumar1-eightfold force-pushed the shubham/select-combobox-keyboard-aria branch from 1866685 to df85589 Compare June 9, 2026 08:47
@shubham-kumar1-eightfold shubham-kumar1-eightfold changed the title Keyboard navigation and scoped active-descendant for Select comboboxes fix(Select,Panel): keyboard option navigation, accessible name, valid option ids; scope combobox Escape in Panel Jun 9, 2026
@shubham-kumar1-eightfold shubham-kumar1-eightfold changed the title fix(Select,Panel): keyboard option navigation, accessible name, valid option ids; scope combobox Escape in Panel Improve Select combobox keyboard navigation and accessible names, and scope Panel Escape to open comboboxes Jun 9, 2026
…d ids

Option ids now derive from the Select menu id, so the unit assertions use the exact deterministic ids (uniqueId is mocked to the prefix in tests) and the snapshot is regenerated. No behavior change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@shubham-kumar1-eightfold shubham-kumar1-eightfold force-pushed the shubham/select-combobox-keyboard-aria branch from 5653e2d to 2db8c0d Compare June 9, 2026 09:35
@shubham-kumar1-eightfold shubham-kumar1-eightfold force-pushed the shubham/select-combobox-keyboard-aria branch 2 times, most recently from 3e388eb to 76a7b02 Compare June 10, 2026 16:45
…r button

Clear aria-activedescendant when the dropdown closes and scope the focusin handler to this Select's own listbox. Add a clearButtonAriaLabel prop (default 'Clear selection') so the clearable button has an accessible name.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@shubham-kumar1-eightfold shubham-kumar1-eightfold force-pushed the shubham/select-combobox-keyboard-aria branch from 76a7b02 to e833cf3 Compare June 10, 2026 19:49
@shubham-kumar1-eightfold shubham-kumar1-eightfold merged commit 9279ea0 into main Jun 10, 2026
7 of 9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants