From 12ede4b79c8d5cffc2b014c7c8a0d2ef1d3bf90d Mon Sep 17 00:00:00 2001 From: Shane Date: Mon, 22 Dec 2025 09:24:32 -0800 Subject: [PATCH] fix(input-password-toggle): improve screen reader announcements (#30885) Issue number: resolves internal --------- ## What is the current behavior? The `ion-input-password-toggle` button uses `role="switch"` with `aria-checked`, causing screen readers like VoiceOver to announce both a state ("On/Off") and an action ("Show/Hide password"). This results in confusing, redundant output such as "On, Hide Password" or "Off, Show Password". ## What is the new behavior? The password toggle button now uses `aria-pressed` instead of `role="switch"` with `aria-checked`. Screen readers announce the action-based label ("Show password" or "Hide password") along with the pressed state, and properly announce state changes when the button is activated. ## Does this introduce a breaking change? - [ ] Yes - [X] No ## Other information [Old Preview](https://ionic-framework-git-main-ionic1.vercel.app/src/components/input-password-toggle/test/basic) [New Preview](https://ionic-framework-git-fw-6920-ionic1.vercel.app/src/components/input-password-toggle/test/basic) Current dev build: ``` 8.7.15-dev.11766421552.180757ca ``` --- core/src/components/button/button.tsx | 1 + .../input-password-toggle/input-password-toggle.tsx | 3 +-- .../test/a11y/input-password-toggle.e2e.ts | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/core/src/components/button/button.tsx b/core/src/components/button/button.tsx index 9eecd0d2c6..0e4741f4ac 100644 --- a/core/src/components/button/button.tsx +++ b/core/src/components/button/button.tsx @@ -170,6 +170,7 @@ export class Button implements ComponentInterface, AnchorInterface, ButtonInterf */ @Watch('aria-checked') @Watch('aria-label') + @Watch('aria-pressed') onAriaChanged(newValue: string, _oldValue: string, propName: string) { this.inheritedAttributes = { ...this.inheritedAttributes, diff --git a/core/src/components/input-password-toggle/input-password-toggle.tsx b/core/src/components/input-password-toggle/input-password-toggle.tsx index 90b743b41e..65ad20ffba 100644 --- a/core/src/components/input-password-toggle/input-password-toggle.tsx +++ b/core/src/components/input-password-toggle/input-password-toggle.tsx @@ -126,9 +126,8 @@ export class InputPasswordToggle implements ComponentInterface { color={color} fill="clear" shape="round" - aria-checked={isPasswordVisible ? 'true' : 'false'} aria-label={isPasswordVisible ? 'Hide password' : 'Show password'} - role="switch" + aria-pressed={isPasswordVisible ? 'true' : 'false'} type="button" onPointerDown={(ev) => { /** diff --git a/core/src/components/input-password-toggle/test/a11y/input-password-toggle.e2e.ts b/core/src/components/input-password-toggle/test/a11y/input-password-toggle.e2e.ts index 0493509dc2..da9846ef9d 100644 --- a/core/src/components/input-password-toggle/test/a11y/input-password-toggle.e2e.ts +++ b/core/src/components/input-password-toggle/test/a11y/input-password-toggle.e2e.ts @@ -22,7 +22,7 @@ configs({ directions: ['ltr'] }).forEach(({ title, config }) => { }); test.describe(title('input password toggle: aria attributes'), () => { - test('should inherit aria attributes to inner button on load', async ({ page }) => { + test('should have correct aria attributes on load', async ({ page }) => { await page.setContent( ` @@ -35,9 +35,9 @@ configs({ directions: ['ltr'] }).forEach(({ title, config }) => { const nativeButton = page.locator('ion-input-password-toggle button'); await expect(nativeButton).toHaveAttribute('aria-label', 'Show password'); - await expect(nativeButton).toHaveAttribute('aria-checked', 'false'); + await expect(nativeButton).toHaveAttribute('aria-pressed', 'false'); }); - test('should inherit aria attributes to inner button after toggle', async ({ page }) => { + test('should update aria attributes after toggle', async ({ page }) => { await page.setContent( ` @@ -51,7 +51,7 @@ configs({ directions: ['ltr'] }).forEach(({ title, config }) => { await nativeButton.click(); await expect(nativeButton).toHaveAttribute('aria-label', 'Hide password'); - await expect(nativeButton).toHaveAttribute('aria-checked', 'true'); + await expect(nativeButton).toHaveAttribute('aria-pressed', 'true'); }); }); });