Skip to content

Instantly share code, notes, and snippets.

@smhigley
Last active May 31, 2022 23:57
Show Gist options
  • Save smhigley/5eb3ee829f1ee963c01397ba4db2da1a to your computer and use it in GitHub Desktop.
Save smhigley/5eb3ee829f1ee963c01397ba4db2da1a to your computer and use it in GitHub Desktop.

Content variations & semantic structure in selectmenu

The purpose of this gist is not to directly recommend a specific implementation of a selectmenu element, but instead to lay out how different variations in content will affect the semantics and keyboard handling needed to make it accessible.

1. Secondary actions on options

Secondary actions refers to when individual options have actions that may be taken in addition to the primary selection action:

screenshot of a listbox with a group titled Recent Searches with two options, pineapple and mango, that each have remove buttons. It is followed by a group titled Results with additional fruits.

These additional actions must fulfill all of the following criteria:

  1. Each action is associated with one and only one specific option.
  2. Each action is a single focusable element -- for example a menu cannot be a secondary action, but individual menuitems could be.
  3. The actions are always present in the DOM

These would then ideally be surfaced by screen readers when interacting with the option with a cue like "actions available" or "3 actions available." The selectmenu itself would need to handle keyboard access to the additional actions, likely with left/right arrows moving between the actions and primary option, and up/down arrows always moving to the previous or next option.

Based on the ARIA Secondary actions proposal, the markup would look similar to this if it were marked up using ARIA attributes:

<button role="combobox" aria-expanded="true" aria-controls="popup" aria-haspopup="listbox" aria-activedescendant="option1">
<div role="listbox" id="popup">
  <div role="option" id="option1" aria-actions="option1-pin">
    Apple
    <button id="option1-pin>Pin</button>
  </div>
  <div role="option" id="option2" aria-actions="option2-pin">
    Banana
    <button id="option2-pin>Pin</button>
  </div>
  <div role="option" id="option3">Cherry</div>
</div>

2. Additional actions before or after options

Instead of having secondary actions that are associated with specific options, this variation deals with including top-level actions or controls that are within the popup but outside of the listbox/set of options.

screenshot of the Windows emoji picker, with tabs along the top, a search input, a grid of emoji, and a gif search link

The emoji picker shows a tab control, search input, and gif search link in addition to the selectable emojis themselves.

screenshot of a standard datepicker with a day grid, a month picker, and previous/next month buttons

This datepicker implementation has buttons to move between months or years, or go to today's date

This is where the semantics of the popup would change from a listbox to a dialog, and it would likely send focus within the popup when it opens, and trap tab focus with tabbing moving between the listbox and the additional controls.

As a listbox pattern is a long-understood construct, the inclusion of the additional buttons before and after the listbox would not be as discoverable were they to just be 'included'. Buttons within a listbox can also add more concrete accessibility bugs, e.g. with unwanted mode switching on Windows screen readers, or interfering with position and size of set calculations. However, by including a parent role=dialog as the popup container, this moves the buttons out of the listbox and helps set the user expectation that this popup likely contains a variety of controls.

A simplified example might have these semantics:

<button role="combobox" aria-expanded="true" aria-controls="popup" aria-haspopup="dialog">
<div role="dialog" id="popup">
  <button>Select All</button>
  <div role="listbox">
    <div role="option" id="option1">Apple</div>
    <div role="option" id="option2">Banana</div>
    <div role="option" id="option3">Cherry</div>
  </div>
  <button>Create New +</button>
  <button>Search for more</button>
</div>

3. Additional actions in between options

There isn't really a good pattern for this. My personal opinion is that this should not be supported. A very determined developer could style a single option to appear as a button, and it would probably be a better end result than the browser providing a lone button within a listbox or splitting the other options into two or more listboxes.

There are essentially three semantic options for making this work:

  1. Split the options into two or more separate listboxes. This would be the most literal approach, but would create an unexpected keyboard experience, since it would essentially mean users do not arrow between all options -- they would arrow through the first listbox, and then need to tab to reach the second set of options.
  2. Drop a semantic button (or other control) inside a listbox. This creates a few problems with screen reader experience: buttons, links, and other singular controls trigger mode switching in Windows screen readers, and it has the potential to change the calculation of index and max count for the surrounding options.
  3. Do not provide support for the first two options, and let authors style and treat options as buttons or links. There's no way to prevent this anyway, and it may be the best solution.

4. Grid layout with linear selection

screenshot of a four by three grid of a random assortment of circular color swatches, under the label Recently Used Colors. Cyan is selected, and the rows and columns do not signal any meaningful relationship between colors.

A grid layout of options doesn't necessarily require grid semantics, if the layout is purely for visual presentation, and the items still have linear relationships with eachother. This should be achievable only with CSS without any negative accessibility impact.

5. Grids and trees

screenshot of a colorpicker where each column is a hue, and each row is a shade from light to dark

These colors have row and column relationships, so unlike the earlier colorpicker, grid semantics do make sense here.

If the items within the dropdown have more complex relationships, there's likely no way to alter the standard list-like selectmenu to fit the different semantic and interaction requirements. The keyboard interaction to move between selectable items would be different for each, as would the mechanism to access any secondary actions (since left/right arrows are already used to navigate the base control).

It would theoretically be possible to create a dialog combobox and put a grid or tree within it, but unless there is a separate reason to need a dialog (e.g. extra month/year buttons in a datepicker), the experience wouldn't be ideal in editable combobox scenarios. It's not possible to keep keyboard focus within the text input while interacting with controls in a dialog, since the user needs to be able to tab through an unknown number of controls within the dialog. For non-editable scenarios it likely wouldn't matter as much, but trapping focus within a dialog with a single tab stop might be unintuitive.

@scottaohara
Copy link

Per example 1, instead of just pointing to the secondary actions proposal, include a bit of information here as to examples of how this could work. E.g., how might these actions be surfaced to someone? Via an announcement saying "actions available" and then a user can use right/left arrow keys to cycle through available actions individually and then press enter to confirm?

would add after "... moving between the listbox and the additional controls."

As a listbox pattern is a long-understood construct, the inclusion of the additional buttons before and after the listbox would not be as discoverable were they to just be 'included'. However, by including a parent role=dialog as the popup container, this helps set the expectation that this popup likely contains a variety of controls, and serves as an aural cue to the user to explore the UI to discover exactly what they can interact with via this UI.

regarding example 3, i'm not exactly sure the use case at this point? Maybe the image placeholder would help. Point 2 here is really important though, and something that NVDA/JAWS would specifically have to change so as to not pop someone in and out of scan/forms mode. Each time this comes up in a conversation (including links/buttons inside of composite widgets that have have decades of being interacted with in forms-mode alone) I have wondered how to broach this subject without putting everyone to sleep (more so than i likely already do). AT would need to change here for these patterns and that would be a slow process in getting into the hands of people that would be most likely to be confused by this change.

Re: point three. You're right. It potentially gets rather confusing when you have options that serve to make a selection, and then other options which perform an action. Adds some extra cognitive load to remember the organizational pattern and what the context for a particular action is, when it's x focus stops away from the actual option that gave it context. E.g., [option name] [action 1] [action 2] [action 3] [action what-was-this-4-again?]

@scottaohara
Copy link

Additional thoughts on the desire for having expand/collapse sub options... there could be a case here to forego the idea of listbox > options all together, and instead have selectmenu render a menu popup with menuitem/menuitemradio/menuitemcheckboxes. There is already a blending of the concepts of combobox/menu pattern with selectmenu. maybe there's an argument to be made to lean more towards the menu pattern for the popup, as that provides more behaviors than listbox presently does?

@smhigley
Copy link
Author

I also think exploring the idea of using menu roles with menuitemradio or menuitemcheckbox would be an interesting and potentially really valuable idea.

I think the patterns around when to use secondary actions and when to shift to using a dialog, grid, or tree would largely stay the same so I'm not going to change this gist, but I am starting to buy more and more into that idea :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment