Skip to content

Select: Fix accesibility (label combobox) and restore keyboard navigation - #435

Open
andreas-schultz wants to merge 6 commits into
developfrom
fix/selectAccessibility-CMEM-8156
Open

andreas-schultz wants to merge 6 commits into
developfrom
fix/selectAccessibility-CMEM-8156

Conversation

@andreas-schultz

Copy link
Copy Markdown
Contributor
  • Connect FieldItem labels, helper text, and messages to the non-filterable Select combobox.
  • Keep focus on the combobox and expose the active option to screen readers.
  • Restore predictable Enter, arrow key, Escape, and Tab behavior, with a visible focus indicator.
  • Add tests for accessible descriptions, option selection, and focus handling.

andreas-schultz and others added 4 commits October 2, 2026 17:30
…tion

- Connect FieldItem labels, helper text, and messages to the non-filterable Select combobox.
- Keep focus on the combobox and expose the active option to screen readers.
- Restore predictable Enter, arrow key, Escape, and Tab behavior, with a visible focus indicator.
- Add tests for accessible descriptions, option selection, and focus handling.
- Connect filterable Select search inputs to their listbox popups.
- Expose the labelled combobox without the duplicate inner button for non-filterable Selects in FieldItem.
- Restore button accessibility when a Select becomes filterable, and test both modes
Comment thread src/components/Form/FieldItem.tsx Outdated
/**
* Blueprint renders a non-filterable Select as a trigger button inside a popover target with
* role="combobox". Our Select makes that outer target the keyboard focus stop, while the
* normal input selector above still finds the inner button. The inner button remains a

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why not improving the selector query in connectableInputSelectors directly instead of trying to fix that with additional JS?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Right, changed this.

Comment thread src/components/Form/FieldItem.tsx Outdated
hiddenSelectButtonRef.current = null;
}
if (selectCombobox && inputElement && inputElement.getAttribute("aria-hidden") !== "true") {
inputElement.setAttribute("aria-hidden", "true");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This input element is not necessarily the button from inside the Select. So it can happen that another input element (e.g. another TextField) will be hidden for screen-reader users.

filterable
? undefined
: {
onFocusCapture: (event) => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This focus capture prevents the ability to focus the clear button when the Select option are not filterable. Please compare:

The clear button itself is not usable in the other version by keyboard, too :( Hope this could also fixed.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Having the button embedded is problematic. I have fixed the keyboard usage of the clear button. But there are still other a11y problems because the button gets aria hidden = true, for which there is no clean fix when we want to keep it there.

Comment thread src/components/Form/FieldItem.tsx Outdated
if (labelElement instanceof HTMLLabelElement && labelableElements.includes(inputElement.tagName)) {
if (selectCombobox) {
if (labelElement instanceof HTMLLabelElement && labelElement.htmlFor === inputElement.id) {
labelElement.removeAttribute("for");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The functionality of the label is removed here completely. Why? This harms usability of it for all users.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Because the for is invalid in case of the div combobox. Here, in this version, there was still the problem that labelElement would maybe not point to the combobox div, but e.g. to a button.

Comment thread src/components/Select/Select.tsx Outdated

const handleActiveItemChange: BlueprintSelectProps<T>["onActiveItemChange"] = (activeItem, isCreateNewItem) => {
if (!filterable && comboboxRef.current) {
const matchingItem = itemIds.current.find(({ item }) => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is this a rebuilt executeItemsEqual that can be imported from @blueprintjs/select?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I will replace it with executeItemsEqual

- Distinguish non-filterable comboboxes from filterable Select trigger buttons
- Exclude hidden inputs from automatic field connections
- Reuse generic label and description wiring and preserve custom label references
- Clean up generated attributes when the control target changes
- Add regression tests for Select discovery and mode changes
- Focus custom controls when their FieldItem label is clicked
- Preserve native label behavior and respect disabled controls
- Support Tab, Shift+Tab, Enter and Space on Select’s clear action
- Prevent clearing and tooltip updates from opening options or stealing focus
- Reuse Blueprint’s item comparison helper
- Add regression tests for label focus and keyboard navigation
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants