Repository navigation
Select: Fix accesibility (label combobox) and restore keyboard navigation - #435
andreas-schultz wants to merge 6 commits into
Conversation
andreas-schultz
commented
Oct 2, 2026
- 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.
…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
| /** | ||
| * 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 |
There was a problem hiding this comment.
Why not improving the selector query in connectableInputSelectors directly instead of trying to fix that with additional JS?
There was a problem hiding this comment.
Right, changed this.
| hiddenSelectButtonRef.current = null; | ||
| } | ||
| if (selectCombobox && inputElement && inputElement.getAttribute("aria-hidden") !== "true") { | ||
| inputElement.setAttribute("aria-hidden", "true"); |
There was a problem hiding this comment.
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) => { |
There was a problem hiding this comment.
This focus capture prevents the ability to focus the clear button when the Select option are not filterable. Please compare:
- https://62150dbccb2d77003a2a5093-kzvryzudir.chromatic.com/?path=/story/forms-select--clearance-option&args=filterable:!false (v26.2.1)
- https://62150dbccb2d77003a2a5093-qalbrecnsf.chromatic.com/?path=/story/forms-select--clearance-option&args=filterable:!false (this branch)
The clear button itself is not usable in the other version by keyboard, too :( Hope this could also fixed.
There was a problem hiding this comment.
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.
| if (labelElement instanceof HTMLLabelElement && labelableElements.includes(inputElement.tagName)) { | ||
| if (selectCombobox) { | ||
| if (labelElement instanceof HTMLLabelElement && labelElement.htmlFor === inputElement.id) { | ||
| labelElement.removeAttribute("for"); |
There was a problem hiding this comment.
The functionality of the label is removed here completely. Why? This harms usability of it for all users.
There was a problem hiding this comment.
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.
|
|
||
| const handleActiveItemChange: BlueprintSelectProps<T>["onActiveItemChange"] = (activeItem, isCreateNewItem) => { | ||
| if (!filterable && comboboxRef.current) { | ||
| const matchingItem = itemIds.current.find(({ item }) => { |
There was a problem hiding this comment.
Is this a rebuilt executeItemsEqual that can be imported from @blueprintjs/select?
There was a problem hiding this comment.
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