I am trying to make my app more screen reader compatible. I use Google TalkBack. My current obstacle occured while working with Toasts.
I created a button that creates and presents a simple toast. Since the screen reader completely ignores them, I added the following attributes to the DOM:
- role=‘alert’
- aria-live=‘root’
- aria-label=‘message’
Against my expectation the screen reader describes the button twice and the toast afterwards, when i click the button. This only happens once. If I click the button again, the order of read elements is as I wanted it to be from the beginning.
My guess is that the focus jumps back to the button (once) and is therefor read twice.
Does anyone know how I can fix this? What causes the focus to return to the button?
After testing a couple of solutions I was satisfied with the following:
private async createIosToast(message: string, duration: number) {
const toast = await this.toastController.create({
message,
duration,
color: 'primary'
});
toast.setAttribute('role', 'alert');
toast.setAttribute('id', 'toast');
const toastElement = document.getElementById('toast') as HTMLElement;
const activeElement = this.correctActiveElement.getCorrectActiveElement();
// set focus on toast
toast.addEventListener('ionToastWillPresent', async () => {
this.focusService.focus(toastElement);
});
// reset focus
toast.onDidDismiss().then(async () => {
console.log(activeElement);
this.focusService.focus(activeElement);
}, error => {
console.log(error);
});
return toast;
}
The following method is important when developing for android and iOS simultaneous. Since the focus-method sets a tabindex, this method avoids applying it to the wrong element. Otherwise, if the element has a shadow-root, it will be focusable twice (only on android).
getCorrectActiveElement(): HTMLElement {
if (this.platform.is('android') && document.activeElement.shadowRoot != null) {
return document.activeElement.shadowRoot.childNodes[0] as HTMLElement;
} else {
return document.activeElement as HTMLElement;
}
}
This method simply sets the focus and applies a tabindex:
public focus(element: HTMLElement): void {
setTimeout(() => {
element.setAttribute('tabindex', '0');
element.focus();
}, 0);
}
Conclusion: Using this code I minimized my problems. On android will be a couple of unnecessary vibrations I was not able to remove. They are caused by the focus jumping back into the webview. On iOS the toast will be read once, is then interrupted and then read completely.
Tested on Google Pixel and iPhone X.