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 ( -