You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
A ul (ideally, the next sibling element but can be inside a portal if it must)
role="listbox"
id="{collectionId}"
labelled by the same label (using aria-labelledby) or aria-label as for the input
Children are an li with a role="option", aria-selected={isCurrentlyFocusedOption} and an id (That gets used as the descdendantId mentioned above; it can be one, static ID that moves to the appropriate li or individual IDs per li which then gets updated at the aria-activedescendant attribute)
Somewhere on the page, a p
id="{uniqueId-info}"
Can be visually hidden
Communicates the state (draft, loading, saved)
Somewhere on the page, a p
id="{uniqueId-instructions}"
Can be visually hidden
Communicates basic instructions. Something like: "Use up and down arrows to move focus over options. Enter to select. Escape to collapse options."
Focus and keyboard controls
TAB
If an option is currently active, select the current option (same as ENTER) and moves focus (as it normally would)
If no option is active, collapse the listbox and move focus (as it normally would)
When tabbing through the widget, the only tab stop should be the input§
ESC
If the listbox is open, collapse the listbox
Optionally, clear the input field
Optionally, return the state of the typeahead to whatever it was before focus was set on the input
DOWN_ARROW
If the listbox is closed and there are options, expand the listbox and set aria-activedescendant to the first option
If the listbox is expanded and there are subsequent options, set aria-activedescendant to the next option
If the listbox is expanded and there are no subsequent options, set aria-activedescendant to the first option (i.e., wrap around)
UP_ARROW
If there is a previous option, set aria-activedescendant to the previous option
If there are no previous options, set aria-activedescendant to the last option (i.e., wrap around)
ENTER
If an option is currently active, select the option, collapsing the listbox and clearing the input field
Notes
† Swapping the value of aria-autocomplete isn't supposed to be a dynamic thing but the spec is vague on how to handle dynamic results. It seems to work though so I think we roll with it.
Δ Using listbox here instead of grid which would also be appropriate to simplify both the implementation and the user interaction. Because grid popups are so much less common, it'll be a more difficult pattern for users to pick up.
‡ These properties follow the ARIA 1.0 spec, not the new ARIA 1.1 spec but the support is best to combine approaches
§ Using aria-activedescendant vs DOM focus is debatable. The spec is open to either implementation but I think this is the right decision based on our already TAB heavy interfaces, general lack of strong accessible navigation controls around apps, has a lower chance of throwing a user's focus back to the body, and supports a wider array of possible interfaces (e.g., adding controls at the bottom of results like saved searches wanted to do in an earlier draft UI).
TODOs
ARIA and DOM setup
div[role="combobox"]
aria-expanded={isResultsListOpen}
aria-owns={collectionId}
aria-haspopup="listbox"
Δinput
label[for="inputId"]
oraria-label
autocomplete="off"
aria-expanded="{isResultsListOpen}"
§role="combobox"
§aria-autocomplete="list"
†aria-controls="{collectionId}"
aria-haspopup="listbox"
Δ§aria-activedescendant="{descendantId}"
‡aria-describedby="{uniqueId-info} {uniqueId-instructions}"
ul
(ideally, the next sibling element but can be inside a portal if it must)role="listbox"
id="{collectionId}"
label
(usingaria-labelledby
) oraria-label
as for the inputli
with arole="option"
,aria-selected={isCurrentlyFocusedOption}
and anid
(That gets used as thedescdendantId
mentioned above; it can be one, static ID that moves to the appropriateli
or individual IDs perli
which then gets updated at thearia-activedescendant
attribute)p
id="{uniqueId-info}"
p
id="{uniqueId-instructions}"
Focus and keyboard controls
input
§aria-activedescendant
to the first optionaria-activedescendant
to the next optionaria-activedescendant
to the first option (i.e., wrap around)aria-activedescendant
to the previous optionaria-activedescendant
to the last option (i.e., wrap around)Notes
† Swapping the value of
aria-autocomplete
isn't supposed to be a dynamic thing but the spec is vague on how to handle dynamic results. It seems to work though so I think we roll with it.Δ Using
listbox
here instead ofgrid
which would also be appropriate to simplify both the implementation and the user interaction. Becausegrid
popups are so much less common, it'll be a more difficult pattern for users to pick up.‡ These properties follow the ARIA 1.0 spec, not the new ARIA 1.1 spec but the support is best to combine approaches
§ Using
aria-activedescendant
vs DOM focus is debatable. The spec is open to either implementation but I think this is the right decision based on our already TAB heavy interfaces, general lack of strong accessible navigation controls around apps, has a lower chance of throwing a user's focus back to thebody
, and supports a wider array of possible interfaces (e.g., adding controls at the bottom of results like saved searches wanted to do in an earlier draft UI).An incomplete example
https://codepen.io/myasonik/pen/LYYEYXb?editors=0010
References
aria-autocomplete
specaria-haspopup
specaria-activedescendant
aria-expanded
The text was updated successfully, but these errors were encountered: