Home > Software design >  How to use role="listbox" with role="option" children that aren't direct de
How to use role="listbox" with role="option" children that aren't direct de

Time:12-10

My html looks something like this:

<div id="languagepickertrigger" aria-role="listbox" aria-label="Language Picker" tabindex="0">Pick a Language</div>
<div id="options">
  <div role="option" tabindex="0">English</div>
  <div role="option" tabindex="0">Spanish</div>
  <div role="option" tabindex="0">German</div>
</div>

I'm aware the role="listbox" docs mention the options need to be direct descendants of the element with the listbox role. Is it possible to override this somehow? Does something like aria-childrenof=languagepickertrigger"?

Thanks

CodePudding user response:

You got the right idea. You want to use aria-owns. If you can't directly nest children in the DOM and the elements are "siblings" instead, that's where aria-owns comes into play.

<div aria-owns="id1 id2 id3" id="languagepickertrigger" aria-role="listbox" aria-label="Language Picker" tabindex="0">Pick a Language</div>
<div id="options">
  <div id="id1" role="option" tabindex="0">English</div>
  <div id="id2" role="option" tabindex="0">Spanish</div>
  <div id="id3" role="option" tabindex="0">German</div>
</div>

CodePudding user response:

There are a few adjustments you should make to follow accepted listbox patterns where the trigger is separate.

But to answer the initial question you want to use aria-haspopup="listbox"!

There is no need to associate the control with something like aria-owns or aria-controls as focus management should be used on a pattern like this (but adding them won't hurt, just make sure they point to the <ul> that contains your options in the below example).

Example code, with some adjustments

<button id="languagepickertrigger" aria-haspopup="listbox" aria-expanded="false">Pick a Language</button>
<ul id="options" role="listbox" `aria-activedescendat="option1" aria-label="pick a language" tabindex="-1">
  <li id="option1" role="option" tabindex="-1" aria-selected="true">English</li>
  <li id="option2" role="option" tabindex="-1">Spanish</li>
  <li id="option3" role="option" tabindex="-1">German</li>
</ul>

Changes

First of all swapping a <div> for a <button> to open the listbox. If there is a semantically appropriate element you can use then use it as it will save you a lot of effort!

Signalling this is a listbox

We can then let screen reader users know that this is part of a listbox widget and is the trigger to open it with aria-haspopup="listbox".

Notice that I removed the role="listbox" from the button that controls it and instead onto the <ul> below. This is because it needs to sit on the actual list and not on the element that controls it in order to announce correctly.

Using an <ul> to improve the screen reader experience

Also notice that I changed your <div>s into an <ul> and <li>s. This is useful for screen readers as it then announces how many options there are within your listbox.

Keyboard controls, focus management and tabindex

Another thing I changed is your tabindex setup.

A listbox is a single control and so you should be able to tab to it and tab away from it.

Navigation within a listbox is done with the up and down arrow keys primarily.

So I changed the tabindex to -1 so that you can manage focus programatically.

There are also expectations that you can:

  • select the first item with the Home key
  • select the last item with the End key
  • cycle through and select items using the first letter of an entry (so pressing "G" should focus "German", pressing it again would focus "Greek" if that was in the list).
  • typing multiple characters in quick succession will select an item beginning with that combination of letters (so "Ger" for German, "Gre" for Greek).

As all of these require focus management that is why I changed the tabindex to -1 so that you can programatically focus an item but it isn't added to the focus order of the page (which would be annoying for keyboard users).

aria-expanded

The last thing is that the button that opens the listbox should have aria-expanded="true" added when the listbox is open.

This should then either be removed or toggled to aria-expanded="false" when the listbox is closed.

In the above example I have added aria-expanded="false" to the button as listboxes default to closed normally, so when the button is clicked you open the drop down, manage the focus and then toggle it to aria-expanded="true" on the button.

Beyond the scope of this answer

You will also see aria-activedescendant and aria-selected that are required to make this work.

  • Related