Skip to content

Accessibility

Liam DeBeasi edited this page Jul 7, 2021 · 4 revisions

Accessibility

Checkbox

Example Components

VoiceOver

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>
  );
}

NVDA

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>
  );
}

Labels

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}
  />

Hidden Input

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.

Known Issues

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.

Switch

Example Components

Voiceover

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>
  );
}

NVDA

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>
  );
}

Labels

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}
  />

Hidden Input

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.

Known Issues

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

Clone this wiki locally