From bb40a1efe71237075db2f3a536eddeb1d7c400fc Mon Sep 17 00:00:00 2001 From: Shane Date: Fri, 28 Mar 2025 09:15:03 -0700 Subject: [PATCH 1/6] fix(checkbox): ensure proper visual selection when navigating via VoiceOver in Safari (#30300) Issue number: resolves internal --------- ## What is the current behavior? Currently, MacOS voice over on Safari does not recognize ion-checkbox correctly and fails to highlight the element properly ## What is the new behavior? By adding the role property to the host element, we're correctly identifying ion-checkbox as a checkbox so Safari knows how to handle it. ## Does this introduce a breaking change? - [ ] Yes - [X] No ## Other information --------- Co-authored-by: Brandy Smith Co-authored-by: Maria Hutt --- core/src/components/checkbox/checkbox.scss | 6 +++++- core/src/components/checkbox/checkbox.tsx | 24 ++++++++++++++++++++-- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/core/src/components/checkbox/checkbox.scss b/core/src/components/checkbox/checkbox.scss index 9cb35cca8f5..438add273d0 100644 --- a/core/src/components/checkbox/checkbox.scss +++ b/core/src/components/checkbox/checkbox.scss @@ -111,8 +111,12 @@ display: none; } +/** + * The native input must be hidden with display instead of visibility or + * aria-hidden to avoid accessibility issues with nested interactive elements. + */ input { - @include visually-hidden(); + display: none; } .native-wrapper { diff --git a/core/src/components/checkbox/checkbox.tsx b/core/src/components/checkbox/checkbox.tsx index 2dbfbedbd1f..3b9c3e6724a 100644 --- a/core/src/components/checkbox/checkbox.tsx +++ b/core/src/components/checkbox/checkbox.tsx @@ -31,6 +31,7 @@ import type { CheckboxChangeEventDetail } from './checkbox-interface'; }) export class Checkbox implements ComponentInterface { private inputId = `ion-cb-${checkboxIds++}`; + private inputLabelId = `${this.inputId}-lbl`; private helperTextId = `${this.inputId}-helper-text`; private errorTextId = `${this.inputId}-error-text`; private focusEl?: HTMLElement; @@ -181,6 +182,15 @@ export class Checkbox implements ComponentInterface { this.ionBlur.emit(); }; + private onKeyDown = (ev: KeyboardEvent) => { + if (ev.key === ' ') { + ev.preventDefault(); + if (!this.disabled) { + this.toggleChecked(ev); + } + } + }; + private onClick = (ev: MouseEvent) => { if (this.disabled) { return; @@ -250,14 +260,23 @@ export class Checkbox implements ComponentInterface { } = this; const mode = getIonMode(this); const path = getSVGPath(mode, indeterminate); + const hasLabelContent = el.textContent !== ''; renderHiddenInput(true, el, name, checked ? value : '', disabled); + // The host element must have a checkbox role to ensure proper VoiceOver + // support in Safari for accessibility. return ( -