feat(react-search): add SearchBox typeahead/autocomplete Storybook story and design spec#35875
feat(react-search): add SearchBox typeahead/autocomplete Storybook story and design spec#35875
Conversation
…HBOX-SPEC.md Co-authored-by: ValentinaKozlova <11574680+ValentinaKozlova@users.noreply.github.com>
📊 Bundle size report✅ No changes found |
|
Pull request demo site: URL |
packages/react-components/react-search/library/docs/TYPEAHEAD-SEARCHBOX-SPEC.md
Show resolved
Hide resolved
smhigley
left a comment
There was a problem hiding this comment.
Thanks so much for making this PR! Overall the semantics look good, just a few notes around handling specific screen reader behaviors (screen readers are weird and especially so with comboboxes 😅)
| /> | ||
|
|
||
| {/* Live region announces result count for screen readers */} | ||
| <div aria-live="polite" aria-atomic="true" className={styles.visuallyHidden}> |
There was a problem hiding this comment.
It would be better to use the useTypingAnnounce utility -- it handles things like rapid/overlapping changes (when an id is passed), clears out the text after it's announced so it isn't reachable by screen reader users inline as it would be here, and also uses document.ariaNotify in browsers that support it.
The reason to use useTypingAnnounce over useAnnounce is because it handles the reason why live regions that fire while the user is typing are problematic -- it prevents the live region from firing until the user pauses text input so it doesn't interfere with keyboard echo.
(I love the inclusion of the live region to let screen reader users know about the suggestions 😊)
| } else if (e.key === 'Escape') { | ||
| setIsOpen(false); | ||
| setFocusedIndex(-1); | ||
| } |
There was a problem hiding this comment.
A couple additions that are related to the way screen readers handle "focus" (not keyboard focus, but the screen reader cursor) and aria-activedescendant -- when aria-activedescendant is set or changed, SR focus gets pulled out of the input and to the active option. That's generally good, but also can interfere with interacting with the text in the input. Typing will automatically pull SR focus back to the input without our interference, but arrowing back and forth to move the text insertion cursor will not.
So, the fix is to also listen to ArrowLeft and ArrowRight, and clear the value for activedescendant when those are used. (we also do this in Combobox, in useInputTriggerSlot)
This probably needs to be handled in a separate state from focusedIndex, since if the user were to start up/down arrowing again, they should start again from the same place they were previously.
| // Close if focus leaves both the input and the listbox | ||
| const relatedTarget = e.relatedTarget as Node | null; | ||
| const listboxEl = document.getElementById(listboxId); | ||
| if (!listboxEl?.contains(relatedTarget)) { |
There was a problem hiding this comment.
it should also check if the clear button is focused and not dismiss in that case either. Right now, since it dismisses when clear is focused, VoiceOver on macOS & iOS and Android Talkback can't access the list of suggestions.
| aria-controls={listboxId} | ||
| aria-expanded={showDropdown || noResults} | ||
| aria-activedescendant={activedescendant} | ||
| role="combobox" |
There was a problem hiding this comment.
We actually don't want role=combobox here for a few reasons:
- it overrides the semantics from
type=search, which are more helpful in describing the component's intended purpose in this case - screen reader users tend to go looking for text inputs / "edit boxes" when looking for search functionality, and the combobox role will prevent the input from being accessed that way
- search inputs can usually be used without selecting an item (i.e. just searching custom typed text), and the combobox role implies that isn't possible
- the activedescendant functionality will still work fine without the combobox role, and the live region does a really good job at letting the user know there are suggestions available even without the role
The
SearchBoxcomponent lacked any typeahead (autocomplete) guidance or implementation, leaving users without a clear path to building search-with-results patterns recommended by the Fluent 2 design docs.Changes
New:
TYPEAHEAD-SEARCHBOX-SPEC.mdAdded the design spec (referenced in the issue) documenting the proposed API surface, component states, interaction model, and accessibility requirements for a typeahead SearchBox pattern.
New:
SearchBoxTypeahead.stories.tsxComplete Storybook story demonstrating the composable typeahead pattern using existing primitives:
Covers:
Spinnerinside the dropdownArrowDown/ArrowUp,Enterto select,Escapeto dismissrole="combobox",aria-activedescendant,aria-liveregion for result count announcementsvisuallyHiddenclass instead of inline stylesUpdated:
SearchBoxBestPractices.md+index.stories.tsxAdded a best-practice recommendation pointing to the typeahead story, and exported the new story from the component's story index.
Original prompt
📱 Kick off Copilot coding agent tasks wherever you are with GitHub Mobile, available on iOS and Android.