Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Investigate creating an accessible custom select/dropdown component #16473

Closed
tellthemachines opened this issue Jul 9, 2019 · 58 comments · Fixed by #17926
Closed

Investigate creating an accessible custom select/dropdown component #16473

tellthemachines opened this issue Jul 9, 2019 · 58 comments · Fixed by #17926
Assignees
Labels
[Focus] Accessibility (a11y) Changes that impact accessibility and need corresponding review (e.g. markup changes). Needs Accessibility Feedback Need input from accessibility [Status] In Progress Tracking issues with work in progress

Comments

@tellthemachines
Copy link
Contributor

Is your feature request related to a problem? Please describe.

HTML select elements are not visually customisable, and in some circumstances we need to customise the options in a dropdown. When showing available text styles, for example:

Screen Shot 2019-07-09 at 2 09 38 pm

We also need these dropdowns to be fully accessible when used with keyboard, screen readers, voice commands, and any assistive technology.

Describe the solution you'd like

Investigate how to best achieve full accessibility while using non-semantic markup, with ARIA and custom keyboard interaction.
Previous discussion for reference:
#16148

Deque's custom select might be a good solution: https://pattern-library.dequelabs.com/components/selects

React-select is another possibility; there's ongoing work/discussion on accessibility improvements over there: JedWatson/react-select#2456

@tellthemachines tellthemachines added [Focus] Accessibility (a11y) Changes that impact accessibility and need corresponding review (e.g. markup changes). [a11y] Epic labels Jul 9, 2019
@noisysocks
Copy link
Member

cc. @epiqueras who I think was looking into this a little.

@epiqueras
Copy link
Contributor

It's @enriquesanchez 😆

@enriquesanchez
Copy link
Contributor

enriquesanchez commented Aug 6, 2019

I tested both Deque's custom select and React-select on Mac (Safari + VoiceOver) and Windows (Firefox + NVDA).

Deque's custom select worked like charm in both cases. React-select did not work well for me, I had trouble getting the screen reader to properly read aloud the available options.

My vote goes for moving forward with Deque's custom select, I think it would allow us to have custom styling on dropdowns (for font selection, text size, etc.) like we used to while also being accessible.

57712309-03e57300-7679-11e9-95a2-e9f36eb31ba2

I'm adding the 'Needs Accessibility Feedback' label in order to get more eyes from the a11y team and see what they think.

@enriquesanchez enriquesanchez added the Needs Accessibility Feedback Need input from accessibility label Aug 6, 2019
@epiqueras
Copy link
Contributor

The discussions in #16666, are relevant here.

...a single input component when there are other lightweight alternatives that could power the accessibility foundations of any input type.

I would start by looking at https://ui.reach.tech or https://github.com/downshift-js/downshift if we need a lower level API.

@afercia
Copy link
Contributor

afercia commented Aug 6, 2019

The discussions in #16666, are relevant here.

On #16666 what's being discussed is an "autocompleter", which is a bit different from a select replacement. I've commented there, see #16666 (comment)

in some circumstances we need to customise the options in a dropdown

Assuming Gutenberg really needs a <select> replacement:

In the last 5 years the accessibility team tested various select-replacement libraries. Most of them are not accessible:

Here's the Select2 accessibility testing and evaluation the a11y team posted on September 2015: https://make.wordpress.org/accessibility/2015/09/07/accessibility-usertest-select2/

See also
#5921 (comment)
(24 May 2018)

Since then, some new select replacement like SelectWoo and react-select improved some of the accessibility issues but they're still not fully accessible.

#5921 (comment)
(2 Apr 2018)

From an accessibility perspective, react-select is not so different from Select2. Unfortunately, it is not accessible.

Both Select2 and react-select have accessibility-related open issues and some work is (slowly) being done. As of today, they're still not accessible.

The Deque custom select looks promising and maybe it would be a nice opportunity to collaborate with Deque to refine some details.

Finally, looks like some W3C folks are considering to standardise and make fully styleable the native <select> element and other form controls: https://twitter.com/gregwhitworth/status/1150771325075542016

The proposal is included in the Web Incubator Community Group (WICG) and it's in an investigation phase. [Edit] This link is dead, please refer to https://github.com/WICG/open-ui and https://open-ui.org/

@epiqueras
Copy link
Contributor

epiqueras commented Aug 6, 2019 via email

@tellthemachines
Copy link
Contributor Author

Accesible autocompleters/comboboxes and dropdowns/selects usually share a lot of code.

Selects and autocompletes may look a bit similar, but their purpose is quite different: select presents you with a limited list of choices from which you can select one or more options; autocomplete is essentially a search type input with a bit of added functionality. Here's a really good talk about what it takes to build an accessible autocomplete: https://www.youtube.com/watch?v=_w6KvvN9cWw (spoiler: it's anything but easy 😄)

My take on this is that it's better to have two solid single-purpose components than one bloated one that tries to do everything at once. There may be utility functions, e.g. for keyboard navigation, that we can share between components, but they shouldn't have much more in common than that.

@epiqueras
Copy link
Contributor

epiqueras commented Aug 7, 2019 via email

@tellthemachines
Copy link
Contributor Author

Comboboxes are dropdowns with dynamic options derived from a text input

Comboboxes are input elements where a dropdown with options is rendered based on what you type in the input. Because they are search type inputs, they need to have a reset button and a search button too.
Selects are not inputs. You can't type into them, though you can use your keyboard to pick a specific option. When you click or press space on the select, all the available options are presented in a dropdown. There's good reason for there to be a specific HTML element dedicated to them 🙂

Take a look at the code for the libraries I suggested, Reach and Downshift.

Reach UI is not production ready. They don't have a select component, and their combobox example is broken: typing in any letter brings up the whole list, which is not expected behaviour. For reference, here are some examples of how comboboxes are supposed to work: https://www.w3.org/TR/wai-aria-practices/examples/combobox/aria1.1pattern/listbox-combo.html

According to discussion here we've already tried downshift and it wasn't accessible enough. Given that they have an open issue from Nov 2018 to look into accessibility issues, it doesn't look like they are making much progress in that area.

@epiqueras
Copy link
Contributor

epiqueras commented Aug 7, 2019 via email

@afercia
Copy link
Contributor

afercia commented Aug 7, 2019

Procedural note 🙂Please avoid to quote the whole previous comment when replying. While the GitHub UI hides the previous comment, the notification emails show both the reply and the previous comment. Reading through all that quoted text is a terrible experience. Thank you!

@epiqueras
Copy link
Contributor

I don't add it. It's the GitHub reply to email flow that does it.

@enriquesanchez
Copy link
Contributor

Thanks for reviewing this @afercia!

collaborate with Deque to refine some details

I'm curious to know if you found any issues with Deque's implementation? I know you had some initial observations on #16148.

@afercia
Copy link
Contributor

afercia commented Aug 7, 2019

@enriquesanchez thank you for reviewing and for the ping on Slack!

Testing the two selects on https://pattern-library.dequelabs.com/components/selects with screen readers on Windows, here's some random observations compared to native behavior:

native <select> elements:

  • both JAWS and NVDA announce them as combo box
  • NVDA + Firefox announce the select state "collapsed" and "expanded" at any time
  • JAWS + Chrome announce the "expanded" state when opening the select pressing Spacebar
  • both switch to "forms mode"
  • on Windows, it's possible to change the options even keeping the select closed: this is standard behavior on Windows
  • when changing options with the select closed, screen readers announce only the option value
  • when the select is open, screen readers announce the option value and their position in the set: n of n

the Deque select:

  • JAWS + Chrome: they announce the initially focused element as "button menu" and the list of options as "listbox"
  • NVDA + Firefox: "button subMenu" and "list"
  • using aria-haspopup="listbox" is a bit arguable, as support is still poor
  • initially, the Deque select doesn't have an aria-expanded attribute: when landing on it, the state is not announced
  • the aria-expanded attribute is set only after the Deque select is opened for the first time
  • with both browser / screen reader combinations, depending also on the navigation mode, either the screen reader never switches to forms mode or it does that only when opening the select

To me, the most important issues are:

  • the difference in the announced role: not sure users will understand what a "button menu" / "button subMenu" is compared to the "combo box" they're used to
  • the lack of the initial expanded state indication: sometimes, aria-expanded is the only clue something is actionable
  • the missing (or quirky) switch to "forms mode"
  • the inability to change the options even with the select closed: this is standard on Windows and expected behavior

All of this demonstrates that, even if well thought and coded:

“The thing is, non-native controls never fully match native element behavior.”
https://twitter.com/ewaccess/status/1157339317142007808

That said, screen readers are just one assistive technology 🙂I'd like to see the Deque select tested at least with a couple more assistive technologies:

  • speech recognition software
  • switch control

@afercia
Copy link
Contributor

afercia commented Aug 7, 2019

Couple screenshots:

Native <select> With Firefox + NVDA:

Screenshot (15)

Deque select With Firefox + NVDA:

Screenshot (14)

@enriquesanchez
Copy link
Contributor

@afercia thanks for the thorough review!

To summarize, a custom select should:

  1. announce its role as combobox
  2. annonce expanded/collapsed state
  3. switch to forms mode
  4. change the options even when the select closed (Windows)
  5. announce the option value and their position (n of n)

Would this be a reasonable list of requirements for any custom select to be considered?

@afercia
Copy link
Contributor

afercia commented Aug 8, 2019

@enriquesanchez yes that would be great.

announce its role as combobox

This one is a bit tricky. Browsers (at least Chrome and Firefox) expose a native <select> element with a role combobox, hence screen readers announce it as a combobox. However, the ARIA role combobox has a different meaning (and different expected interaction). Unsure what to do.

@silviuaavram
Copy link

silviuaavram commented Aug 10, 2019

Have you tried useSelect() from https://github.com/silviuavram/downshift-hooks ?

@enriquesanchez
Copy link
Contributor

Hey @silviuavram! Thanks for the suggestion. I ran a quick test with useSelect():

On Safari + VoiceOver (Mac):

❌ announce its role as combobox
❌ announce expanded/collapsed state — announces collapsed but not expanded
✅ switch to forms mode — VO recognizes it as a form element
change the options even when the select is closed (Windows)
✅ announce the option value and their position (n of n)

Firefox + NVDA (Windows):

❌ announce its role as combobox
✅ announce expanded/collapsed state
✅ switch to forms mode
❌ change the options even when the select is closed (Windows)
✅ announce the option value and their position (n of n)

Also, and while this may be only a styling issue, the element looks like a button instead of a select element.

@silviuaavram
Copy link

Thank you @enriquesanchez for the feedback. I'd be happy to follow up on your points. I plan to merge useSelect in Downshift soon, so I am interested in making it ready for production.
https://github.com/downshift-js/downshift

For combobox I am not sure if that is expected. If we are talking about the select widget, then that is different from the combobox one. The combobox always involves an input instead of a button, as it is the case for the select.

For expanded I don't think it's possible to announce expanded state (maybe with Virtual Cursor while the menu is open). By default, normal focus moves to the list after it is open.

For changing the options when the select is closed, I am not sure what you mean. Is this a select feature as well, can you describe it?

You are responsible with the styling, so it is up to you how you do it. But for a select, usually the trigger for the menu is a button. On VoiceOver it is announced as Pop-up button.

Maybe we can sync more on requirements and also if you can provide an example with a fully working widget, anywhere on the internet, maybe that will be helpful as well.

All the best!

@enriquesanchez
Copy link
Contributor

enriquesanchez commented Aug 15, 2019

Hey @silviuavram!

For combobox I am not sure if that is expected

On Windows, a native select element is announced by screen readers as a combobox. On Mac, it's announced as a popup button

For expanded I don't think it's possible to announce expanded state

I believe it's possible with aria-expanded.

For changing the options when the select is closed, I am not sure what you mean

This is an option available on Windows. You don't need to expand the list of options in order to scroll through the list.

@enriquesanchez
Copy link
Contributor

@ItsJonQ has been helping me with the implementation of a custom dropdown/select that covers most of the requirements laid out on this issue.

While there are still some rough edges, we want to hear your feedback. I've been testing it with NVDA+Firefox on Windows and VoiceOver+Safari on Mac and would like to hear from others.

The custom dropdown/select can be found on this Codesandbox: https://2gbb9.csb.app

cc. @afercia

@danielweck
Copy link

@enriquesanchez that's a great start, thanks! :)
The visuals could be improved, but the fundamental interaction requirements seem well-implemented.

I spotted one bug: when using the up/down arrow keys inside the popup "menu" to select an option from the list of predefined choices, the HTML page scrolls up/down. Missing preventDefault()?
The space bar seems to be handled correctly, as it can be used as an alternative to the enter key, and does not trigger up/down scrolling.

@danielweck
Copy link

Apple MacOS, Voice Over, Google Chrome web browser:

  • when tabbing into the collapsed "custom font size selector", the current value is not announced.
  • when selecting a value from the popup list using the space bar or the enter key, it is only announced once (subsequent picks are silent)

@silviuaavram
Copy link

If implementing it from the ground up is what you wish, sure, go ahead. useSelect just makes it easier, you don't have to do any coding and it can be customisable as well. For instance, if you really want to add role="combobox" and aria-expanded="false" then you are welcome to add it and still get the rest of the hook's functionality.

I did not add those by default because I follow the W3C design pattern for it, and their specs are different.

@mapk
Copy link
Contributor

mapk commented Oct 4, 2019

Really great testing, @enriquesanchez! Thanks!

While waiting to hear from @silviuavram, can we create a PR with Downshift and make the aria label change ourselves?

cc @enriquesanchez @epiqueras

@silviuaavram
Copy link

That is awesome @enriquesanchez thank you for the feedback!

I will get back to this next week as I'm feeling under the weather now.

But yes, you can create the PR. you can remove aria-labelledBy by passing it as undefined to the getToggleButtonProps and passing aria-label instead. Just make sure to do it also in the getMenuProps, that's labelled by the Label as well.

Also make sure you use latest Downshift (3.3.4). Good luck and leave comments here I will address them once I will feel better.

@epiqueras
Copy link
Contributor

@silviuavram Thanks for all the work here! We're experimenting with this fork: https://codesandbox.io/s/wordpress-components-dropdown-font-size-picker-bwvf2

I however was not able to interact with it with Dragon Speech. I believe this is because the button is using aria-labelledby and that has weak support with speech recognition apps. According to my tests, if we use aria-label instead, then Dragon Speech can interact with it. I was expecting to be able to say 'Click "Font Size"' and have the menu button expand.

This is fixed.

Once expanded though, I wasn't able to select an option. If I said 'Click "Huge"' for example, the popover will collapse and no change would be registered. This one I'm not sure why is happening. Any clues why?

This is still an issue.

Do you have any idea what it might be?

@silviuaavram
Copy link

Added a couple of suggestions in the PR @epiqueras

@spacedmonkey
Copy link
Member

Flagging #16666 and #7385 as related here, as they go a different direction with an accessible select menu as they use accessible-autocomplete package.

Looping in @adamsilverstein as he worked on those PRs.

@silviuaavram
Copy link

@epiqueras I don't own a license of Dragon Speech (and not really sure if I can get a trial and for which product). Hopefully you can find the issue by stateReducer (the console.log suggestion) and if you need more help let me know!

@epiqueras
Copy link
Contributor

@silviuavram Me neither, and it's Windows only 😢

Is there anyone on this thread available for pair programming? 😄

@epiqueras
Copy link
Contributor

Is there anyone on this thread available for pair programming? 😄

With Dragon Home/Speech.*

@afercia
Copy link
Contributor

afercia commented Oct 16, 2019

@grahamarmfield and @ewaccess might be able to help with Dragon, if they have a chance 🙂 They would need a testable page though, not something they need to set up, install pagkaces, etc.

@epiqueras
Copy link
Contributor

@tellthemachines
Copy link
Contributor Author

Is there anyone on this thread available for pair programming? 😄

With Dragon Home/Speech.*

@enriquesanchez did some testing with Dragon on #17418 which was our previous attempt at this.

@afercia
Copy link
Contributor

afercia commented Dec 3, 2019

Looks like the custom select component merged in #17926 misses a few basic things to be considered a working replacement for some basic (not all) features of a native <select> element.

Most importantly, the currently selected value is not exposed programmatically so any software including screen readers won't be able to understand what the currently selected option is.

  • after a value has been set
  • use a screen reader and navigate to the component button
  • the set value is not announced:

Screenshot 2019-12-03 at 17 49 00

This happens because the button is labelled (actually it's labelled twice) so the label value overrides the button content:

Screenshot 2019-12-03 at 17 50 39

Both the <label> element and the aria-label attribute give the button an accessible name that overrides its content. The button content represents the set value but can't be read out.

Inspecting the accessible properties with Firefox, the relevant difference is that this custom component ha an accessible name but no value:

Screenshot 2019-12-03 at 17 59 34

while a native <select> element has also a value:

Screenshot 2019-12-03 at 17 58 35

Note also the role is different, which may be confusing for users.

At the very least there's the need to:

  1. find a way to make the set value announced
  • maybe aria-describedby could be explored for this purpose
  1. remove the aria-label as there's already a visible associated <label> element
  • aria-label is redundant, unless there's a good reason to keep it I can't think of

Worth also mentioning that native <select> element interaction greatly varies across operating systems and browsers as mentioned in previous comments. Users are used to these interaction models and a select-replacement component simply can't replicate them all. This further increases the chances for potential confusion and unexpected interaction. Personally, I still don't see a good reason to have a custom component only for the sake of a visual preview of the font size, at the cost of losing many of the native features of a native HTML <select>.

Reopening to address the two points above. I'd also like this issue and the implementation from #17926 to be discussed in the next accessibility team meeting.

@afercia afercia reopened this Dec 3, 2019
@tellthemachines
Copy link
Contributor Author

aria-label is redundant, unless there's a good reason to keep it I can't think of

We added aria-label after testing with Dragon and verifying that the button would not be activated unless it had the aria-label matching the visible text. It's still open for debate whether it's more intuitive to activate the button by speaking out the label above it or the actual button content though. Perhaps making it equal to the button content would solve the problem?

I'd also like this issue and the implementation from #17926 to be discussed in the next accessibility team meeting.

It would be great to have some more opinions on this! Unfortunately I can't join because the meeting takes place in the middle of the night for me, but more than happy to discuss asynchronously 🙂

@afercia
Copy link
Contributor

afercia commented Dec 5, 2019

Got it. The root problem is:

  • This is still a button that looks like a dropdown (select). While buttons are technically labelable elements, it's very uncommon to have a label associated with a button. Clicking a label should just focus the associated element. In this case instead, clicking the label activates the button
  • The buttons text should always identify the available action (what the button "does). Instead, this button text is the selected value. We've debated at length similar cases, for example the Visibility and Publish date buttons. See Improve "Visibility" and "Publish" labels in Post Settings #470 which is open since April 2017 and still to solve because considerations about visuals prevail on standards and semantics. UI controls should just tell what they do. Instead, some UI controls in Gutenberg have text that is the underlying selected setting or state. This is non-standard and goes against basic usability principles.

Overall, even if this component tries to use ARIA techniques it is still a non-native implementation that reduces the accessibility of this control and as such it's actually an accessibility regression compared to the implementation with a native <select> element. A properly labeled <select> element would work for everyone and also with Dragon.

I'd also like to remind the previous implementation with the custom font size selector was flagged as a WCAG failure by the WPCampus audit in #15319. That's the reason why it was changed to a native <select> following the remediation guidance provided on the issue. There are many non-standard things in this component that it can't be really called "WAI-ARIA compliant custom select" and it will likely be flagged again as a failure by any future accessibility audit.

That said, I do realize others in the team strongly feel in favour of this component because they want a "preview" of the font size. For what is worth, I can't really support this implementation as it comes to a cost of reduced accessibility. I'd like to point out there are other ways to show a preview that haven't been even considered.

@silviuaavram
Copy link

@afercia is right, the button being triggered by the label click is an unfortunate side effect.

  • the htmlFor can be removed from the label but the click on the label will not do anything.
  • the button element can be replaced with something else but I don't know what other element can be used to just accept the focus on label click and not be triggered.

Of course a native select will have its accessibility covered out of the box. You will have to spend effort on styling the thing though. Maybe on other things as well.

Do you think of any improvements that can be done here in terms of accessibility?

@afercia
Copy link
Contributor

afercia commented Dec 5, 2019

Do you think of any improvements that can be done here in terms of accessibility?

I'd start strictly following the pattern described on the ARIA Authoring Practices. The patterns described there are the ones assistive technologies are supposed to support.
https://www.w3.org/TR/wai-aria-practices-1.2/#Listbox
https://www.w3.org/TR/wai-aria-practices-1.2/examples/listbox/listbox-collapsible.html

<span id="exp_elem">Choose an element:</span>
<div id="exp_wrapper">
    <button aria-haspopup="listbox" aria-labelledby="exp_elem exp_button" id="exp_button">
        Neptunium
    </button>
    <ul ...

  • there's no <label> element
  • the button is labelled with aria-labelledby which references two elements:
    • the visible text above the button
    • the button itself

This way, also the selected value within the button will be announced by screen readers e.g.: Choose an element: Neptunium

Then, this should be tested again with Dragon and other speech recognition software, e.g. Voice Control / Dictation on macOS.

Still, the different behaviors and interactions across operating systems and browsers can't be covered by a custom component. That's my main concern as they greatly differ and users are used to them.

@silviuaavram
Copy link

As I understand the aria-label was added to make it work with Dragon Speech. Originally it should have been only an aria-labelledby with the 2 IDs you correctly mentioned since that's the default useSelect behavior. We can try to remove the aria-label again and dig deeper into why it's not workign.

About the htmlFor I am OK with removing it since it causes this toggle issue. However a native <select> works with a <label> by default. It will just move focus on the <select> without opening it. Do you think it's a good common ground? Removing the htmlFor and adding a onClick on the getLabelProps that will focus() the toggleButton?

@afercia
Copy link
Contributor

afercia commented Dec 5, 2019

About the htmlFor I am OK with removing it since it causes this toggle issue

Removing only the htmlFor would produce an orphaned label, which should be avoided. The whole <label> element should be removed instead.

Yes the aria-labelledby with two IDs should be tested again with speech recognition software but that's the only way I can think of to make assistive technologies announce also the currently selected "option" as they would do with a native <select> element.

@silviuaavram
Copy link

The label will not be orphaned, as it will still have the id used in aria-labelledby on the button and menu. So I think we can prop the getLabelProps with {htmFor: undefined} and test it from there. I am pushing for a speech recognition in my org so I can test with that.

@tellthemachines
Copy link
Contributor Author

I'm closing this issue as we now have both CustomSelectControl and ComboboxControl in our components package. Any specific a11y issues with these components should be reported separately. Thanks everyone for the input!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Focus] Accessibility (a11y) Changes that impact accessibility and need corresponding review (e.g. markup changes). Needs Accessibility Feedback Need input from accessibility [Status] In Progress Tracking issues with work in progress
Projects
None yet
Development

Successfully merging a pull request may close this issue.