Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion core/src/components/checkbox/checkbox.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
24 changes: 22 additions & 2 deletions core/src/components/checkbox/checkbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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 (
<Host
role="checkbox"
aria-checked={indeterminate ? 'mixed' : `${checked}`}
aria-describedby={this.getHintTextID()}
aria-invalid={this.getHintTextID() === this.errorTextId}
aria-labelledby={hasLabelContent ? this.inputLabelId : null}
aria-label={inheritedAttributes['aria-label'] || null}
aria-disabled={disabled ? 'true' : null}
tabindex={disabled ? undefined : 0}
onKeyDown={this.onKeyDown}
class={createColorClasses(color, {
[mode]: true,
'in-item': hostContext('ion-item', el),
Expand All @@ -271,7 +290,7 @@ export class Checkbox implements ComponentInterface {
})}
onClick={this.onClick}
>
<label class="checkbox-wrapper">
<label class="checkbox-wrapper" htmlFor={inputId}>
{/*
The native control must be rendered
before the visible label text due to https://bugs.webkit.org/show_bug.cgi?id=251951
Expand All @@ -291,9 +310,10 @@ export class Checkbox implements ComponentInterface {
<div
class={{
'label-text-wrapper': true,
'label-text-wrapper-hidden': el.textContent === '',
'label-text-wrapper-hidden': !hasLabelContent,
}}
part="label"
id={this.inputLabelId}
>
<slot></slot>
{this.renderHintText()}
Expand Down
Loading