web-components
web-components copied to clipboard
[select] item navigation controls suggested by VoiceOver don't work [0.5 day]
What is the problem?
With VoiceOver on
- open https://a11y-vaadin-playground.netlify.app/pages/v22/select.html
- open the first
<vaadin-select>
dropdown - See the suggested controls
However, Control-Option-Shift-Down Arrow does nothing
May be that the dropdown/list-box roles aren't correct so a wrong kind of an announcement is shown
Browsers
- [X] Chrome
- [ ] Firefox
- [ ] Safari
- [ ] Safari on iOS
- [ ] Edge
Screen Readers
- [ ] None
- [ ] NVDA
- [ ] JAWS
- [X] VoiceOver on MacOS
- [ ] VoiceOver on iOS
We need to figure out if it is a problem in avatar-group
and menu-bar
.
Tested this for other components:
vaadin-avatar-group
Chrome
The role is announced as "group". Also, the VoiceOver cursor does not move to item:
screenshot
Safari
The role is announced as "text element" (this is expected, as VoiceOver does not annonce role="option"
, unlike native <option>
element). The VoiceOver cursor is moved to the item in the list-box:
screenshot
vaadin-select
Chrome
Same as in vaadin-avatar-group
: the VoiceOver cursor remains on the vaadin-select-value-button
:
screenshot
Safari
Same as in vaadin-avatar-group
: the VoiceOver cursor is moved to the item in the list-box:
screenshot
vaadin-menu-bar
This component is not affected, most likely because it's using role="menuitem"
which has different item navigation.
Also, the VoiceOver cursor is correctly moved to the item in the overlay when using VoiceOver on Chrome.
One more issue about item navigation in Safari: when moving to different item using Control + Shift + Arrow Down and pressing Enter, the wrong item is selected, as the keyboard focus is not in sync with the VoiceOver cursor 😕
https://user-images.githubusercontent.com/10589913/182384389-22051ff2-5930-45f4-bd38-6cb3facdd5c5.mp4
UPD: this problem also exists in the ARIA examples when using VoiceOver on Chrome 🤷♂️
Regarding the original issue, I could not get VoiceOver outline working on Chrome. Here are some things that I tried:
- Postpone calling
this._menuElement.focus()
untilvaadin-overlay-open
event is fired, - Disable animations for
vaadin-select-overlay
(the way it's done in our visual tests), - Set
aria-controls
attribute on thevaadin-select-value-button
to link it to list-box.
Regardless of those steps, the behavior of the VoiceOver cursor doesn't change:
- When pressing Enter, the VoiceOver cursor remains on
vaadin-select-value-button
- When pressing Arrow Down, the VoiceOver cursor then moves to the whole list-box
https://user-images.githubusercontent.com/10589913/182564309-5e523647-a882-436d-9348-9fb885ad6c89.mp4
Looks like something is wrong with either vaadin-list-box
or vaadin-item
. Using div
with correct roles works.
Also, using custom elements that set correct roles (without using tabindex) works fine, too:
Code example
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Menu button</title>
<script type="module">
import { html, PolymerElement } from '@polymer/polymer';
import { PositionMixin } from '@vaadin/vaadin-overlay/src/vaadin-overlay-position-mixin.js';
import { OverlayElement } from '@vaadin/vaadin-overlay';
customElements.define(
'menu-button-overlay',
class extends PositionMixin(OverlayElement) {
static get is() {
return 'menu-button-overlay';
}
},
);
customElements.define(
'menu-button-list-box',
class extends PolymerElement {
static get is() {
return 'menu-button-list-box';
}
static get template() {
return html`
<style>
:host {
display: block;
}
</style>
<slot></slot>
`;
}
ready() {
super.ready();
this.setAttribute('role', 'listbox');
}
},
);
customElements.define(
'menu-button-item',
class extends PolymerElement {
static get is() {
return 'menu-button-item';
}
static get template() {
return html`
<style>
:host {
display: block;
}
</style>
<slot></slot>
`;
}
ready() {
super.ready();
this.setAttribute('role', 'option');
}
},
);
customElements.define(
'menu-button',
class extends PolymerElement {
static get template() {
return html`
<button id="button" on-click="_onClick">Open</button>
<menu-button-overlay id="overlay" opened="{{opened}}" on-vaadin-overlay-open="_onOverlayOpen">
<template>
<menu-button-list-box>
<menu-button-item aria-selected="false" tabindex="0" style="padding: 5px 10px;"
>Foo</menu-button-item
>
<menu-button-item role="option" aria-selected="false" tabindex="-1" style="padding: 5px 10px;"
>Bar</menu-button-item
>
<menu-button-item role="option" aria-selected="false" tabindex="-1" style="padding: 5px 10px;"
>Baz</menu-button-item
>
</menu-button-list-box>
</template>
</menu-button-overlay>
`;
}
static get properties() {
return {
opened: Boolean,
};
}
ready() {
super.ready();
this.$.overlay.positionTarget = this.$.button;
// this.$.overlay.noVerticalOverlap = true;
}
_onClick() {
this.opened = true;
}
_onOverlayOpen() {
const item = this.$.overlay.content.querySelector('[tabindex="0"]');
item.focus();
}
},
);
</script>
</head>
<body>
<menu-button></menu-button>
</body>
</html>