mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-11-09 08:09:32 +08:00
fix(toast): screen readers now announce toasts when presented (#24937)
resolves #22333
This commit is contained in:
@ -59,6 +59,30 @@ interface ToastOptions {
|
|||||||
interface ToastAttributes extends JSXBase.HTMLAttributes<HTMLElement> {}
|
interface ToastAttributes extends JSXBase.HTMLAttributes<HTMLElement> {}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Accessibility
|
||||||
|
|
||||||
|
### Focus Management
|
||||||
|
|
||||||
|
Toasts are intended to be subtle notifications and are not intended to interrupt the user. User interaction should not be required to dismiss the toast. As a result, focus is not automatically moved to a toast when one is presented.
|
||||||
|
|
||||||
|
### Screen Readers
|
||||||
|
|
||||||
|
`ion-toast` has `aria-live="polite"` and `aria-atomic="true"` set by default.
|
||||||
|
|
||||||
|
`aria-live` causes screen readers to announce the content of the toast when it is presented. However, since the attribute is set to `'polite'`, screen readers generally do not interrupt the current task. Developers can customize this behavior by using the `htmlAttributes` property to set `aria-live` to `'assertive'`. This will cause screen readers to immediately notify the user when a toast is presented, potentially interrupting any previous updates.
|
||||||
|
|
||||||
|
`aria-atomic="true"` is set to ensure that the entire toast is announced as a single unit. This is useful when dynamically updating the content of the toast as it prevents screen readers from announcing only the content that has changed.
|
||||||
|
|
||||||
|
### Tips
|
||||||
|
|
||||||
|
While this is not a complete list, here are some guidelines to follow when using toasts.
|
||||||
|
|
||||||
|
* Do not require user interaction to dismiss toasts. For example, having a "Dismiss" button in the toast is fine, but the toast should also automatically dismiss on its own after a timeout period. If you need user interaction for a notification, consider using [ion-alert](./alert) instead.
|
||||||
|
|
||||||
|
* Avoid opening multiple toasts in quick succession. If `aria-live` is set to `'assertive'`, screen readers may interrupt the reading of the current task to announce the new toast, causing the context of the previous toast to be lost.
|
||||||
|
|
||||||
|
* For toasts with long messages, consider adjusting the `duration` property to allow users enough time to read the content of the toast.
|
||||||
|
|
||||||
<!-- Auto Generated Below -->
|
<!-- Auto Generated Below -->
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
33
core/src/components/toast/test/a11y/index.html
Normal file
33
core/src/components/toast/test/a11y/index.html
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" dir="ltr">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Toast - a11y</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, viewport-fit=cover">
|
||||||
|
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet">
|
||||||
|
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet">
|
||||||
|
<script src="../../../../../scripts/testing/scripts.js"></script>
|
||||||
|
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
|
||||||
|
<script type="module">
|
||||||
|
import { toastController } from '../../../../dist/ionic/index.esm.js';
|
||||||
|
window.toastController = toastController;
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<ion-app>
|
||||||
|
<main>
|
||||||
|
<h1 style="background-color: white">Toast - a11y</h1>
|
||||||
|
|
||||||
|
<button id="polite" onclick="presentToast({ message: 'This is a toast message' })">Present Toast</button>
|
||||||
|
<button id="assertive" onclick="presentToast({ message: 'This is an assertive toast message', htmlAttributes: { 'aria-live': 'assertive' } })">Present Assertive Toast</button>
|
||||||
|
</main>
|
||||||
|
</ion-app>
|
||||||
|
<script>
|
||||||
|
const presentToast = async (opts) => {
|
||||||
|
const toast = await toastController.create(opts);
|
||||||
|
|
||||||
|
await toast.present();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
48
core/src/components/toast/test/a11y/toast.e2e.ts
Normal file
48
core/src/components/toast/test/a11y/toast.e2e.ts
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import { newE2EPage } from '@stencil/core/testing';
|
||||||
|
import { AxePuppeteer } from '@axe-core/puppeteer';
|
||||||
|
|
||||||
|
describe('toast accessibility tests', () => {
|
||||||
|
test('it should not have any axe violations with polite toasts', async () => {
|
||||||
|
const page = await newE2EPage({
|
||||||
|
url: '/src/components/toast/test/a11y?ionic:_testing=true'
|
||||||
|
});
|
||||||
|
|
||||||
|
const ionToastDidPresent = await page.spyOnEvent('ionToastDidPresent');
|
||||||
|
|
||||||
|
await page.click('#polite');
|
||||||
|
|
||||||
|
await ionToastDidPresent.next();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IonToast overlays the entire screen, so
|
||||||
|
* Axe will be unable to verify color contrast
|
||||||
|
* on elements under the toast.
|
||||||
|
*/
|
||||||
|
const results = await new AxePuppeteer(page)
|
||||||
|
.disableRules('color-contrast')
|
||||||
|
.analyze();
|
||||||
|
expect(results.violations.length).toEqual(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it should not have any axe violations with assertive toasts', async () => {
|
||||||
|
const page = await newE2EPage({
|
||||||
|
url: '/src/components/toast/test/a11y?ionic:_testing=true'
|
||||||
|
});
|
||||||
|
|
||||||
|
const ionToastDidPresent = await page.spyOnEvent('ionToastDidPresent');
|
||||||
|
|
||||||
|
await page.click('#assertive');
|
||||||
|
|
||||||
|
await ionToastDidPresent.next();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IonToast overlays the entire screen, so
|
||||||
|
* Axe will be unable to verify color contrast
|
||||||
|
* on elements under the toast.
|
||||||
|
*/
|
||||||
|
const results = await new AxePuppeteer(page)
|
||||||
|
.disableRules('color-contrast')
|
||||||
|
.analyze();
|
||||||
|
expect(results.violations.length).toEqual(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -280,6 +280,8 @@ export class Toast implements ComponentInterface, OverlayInterface {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Host
|
<Host
|
||||||
|
aria-live="polite"
|
||||||
|
aria-atomic="true"
|
||||||
role={role}
|
role={role}
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
{...this.htmlAttributes as any}
|
{...this.htmlAttributes as any}
|
||||||
|
|||||||
Reference in New Issue
Block a user