-
Notifications
You must be signed in to change notification settings - Fork 13.5k
Accessibility
In order for VoiceOver to work properly with a checkbox component there must be a native input
with type="checkbox"
, and aria-checked
and role="checkbox"
must be on the host element. The aria-hidden
attribute needs to be added if the checkbox is disabled, preventing iOS users from selecting it:
render() {
const { checked, disabled } = this;
return (
<Host
aria-checked={`${checked}`}
aria-hidden={disabled ? 'true' : null}
role="checkbox"
>
<input
type="checkbox"
/>
...
</Host>
);
}
It is required to have aria-checked
on the native input for checked to read properly and disabled
to prevent tabbing to the input:
render() {
const { checked, disabled } = this;
return (
<Host
aria-checked={`${checked}`}
aria-hidden={disabled ? 'true' : null}
role="checkbox"
>
<input
type="checkbox"
aria-checked={`${checked}`}
disabled={disabled}
/>
...
</Host>
);
}
A helper function has been created to get the proper aria-label
for the checkbox. This can be imported as getAriaLabel
like the following:
const { label, labelId, labelText } = getAriaLabel(el, inputId);
where el
and inputId
are the following:
export class Checkbox implements ComponentInterface {
private inputId = `ion-cb-${checkboxIds++}`;
@Element() el!: HTMLElement;
...
}
This can then be added to the Host
like the following:
<Host
aria-labelledby={label ? labelId : null}
aria-checked={`${checked}`}
aria-hidden={disabled ? 'true' : null}
role="checkbox"
>
In addition to that, the checkbox input should have a label added:
<Host
aria-labelledby={label ? labelId : null}
aria-checked={`${checked}`}
aria-hidden={disabled ? 'true' : null}
role="checkbox"
>
<label htmlFor={inputId}>
{labelText}
</label>
<input
type="checkbox"
aria-checked={`${checked}`}
disabled={disabled}
id={inputId}
/>
A helper function to render a hidden input has been added, it can be added in the render
:
renderHiddenInput(true, el, name, (checked ? value : ''), disabled);
This is required for the checkbox to work with forms.
When using VoiceOver on macOS, Chrome will announce the following when you are focused on a checkbox:
currently on a checkbox inside of a checkbox
This is a compromise we have to make in order for it to work with the other screen readers & Safari.
When using form components such as ion-input
or ion-toggle
inside of ion-item
, setting disabled="true"
on the form component will disable the entire ion-item
. Both the form component and the label will have a lower opacity and will not receive pointer events.
However, just doing that is not enough as screen readers do not know that the form component associated with the ion-label
is disabled. The reason for this is the disabled
property on a Web Component such as ion-toggle
is not the same as the disabled Attribute. This disabled attribute is only supported on certain HTML elements, not Web Components. As a result, we need to set aria-disabled="true"
on the ion-item
so that the screen reader knows that the item as well as its label and form component are disabled.
How does ion-item
know when to apply aria-disabled
? ion-item
listens for the ionStyle
event emitted from any of the form components. The form components indicate whether or not the component is disabled. For example, here is the relevant code for ion-toggle
: https://github.com/ionic-team/ionic-framework/blob/master/core/src/components/toggle/toggle.tsx#L127-L131. ion-item
looks for this to determine whether or not to set aria-disabled
.
When used inside of ion-item
, most form components are automatically associated with the corresponding ion-label
by adding an id
to the label and then setting aria-labelledby
on the form component. ion-select
is a different story as we need to have screen readers announce both the label value as well as the text of any selected options within ion-select
. As a result, we set an aria-label
on the ion-select
which contains the label text as well as the text of any selected options. If there are no selected options, the placeholder text is used instead.
In order for VoiceOver to work properly with a switch component there must be a native input
with type="checkbox"
and role="switch"
, and aria-checked
and role="switch"
must be on the host element. The aria-hidden
attribute needs to be added if the switch is disabled, preventing iOS users from selecting it:
render() {
const { checked, disabled } = this;
return (
<Host
aria-checked={`${checked}`}
aria-hidden={disabled ? 'true' : null}
role="switch"
>
<input
type="checkbox"
role="switch"
/>
...
</Host>
);
}
It is required to have aria-checked
on the native input for checked to read properly and disabled
to prevent tabbing to the input:
render() {
const { checked, disabled } = this;
return (
<Host
aria-checked={`${checked}`}
aria-hidden={disabled ? 'true' : null}
role="switch"
>
<input
type="checkbox"
role="switch"
aria-checked={`${checked}`}
disabled={disabled}
/>
...
</Host>
);
}
A helper function has been created to get the proper aria-label
for the switch. This can be imported as getAriaLabel
like the following:
const { label, labelId, labelText } = getAriaLabel(el, inputId);
where el
and inputId
are the following:
export class Toggle implements ComponentInterface {
private inputId = `ion-tg-${toggleIds++}`;
@Element() el!: HTMLElement;
...
}
This can then be added to the Host
like the following:
<Host
aria-labelledby={label ? labelId : null}
aria-checked={`${checked}`}
aria-hidden={disabled ? 'true' : null}
role="switch"
>
In addition to that, the checkbox input should have a label added:
<Host
aria-labelledby={label ? labelId : null}
aria-checked={`${checked}`}
aria-hidden={disabled ? 'true' : null}
role="switch"
>
<label htmlFor={inputId}>
{labelText}
</label>
<input
type="checkbox"
role="switch"
aria-checked={`${checked}`}
disabled={disabled}
id={inputId}
/>
A helper function to render a hidden input has been added, it can be added in the render
:
renderHiddenInput(true, el, name, (checked ? value : ''), disabled);
This is required for the switch to work with forms.
When using VoiceOver on macOS or iOS, Chrome will announce the switch as a checked or unchecked checkbox
:
You are currently on a switch. To select or deselect this checkbox, press Control-Option-Space.
There is a WebKit bug open for this: https://bugs.webkit.org/show_bug.cgi?id=196354