Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add accessibility props to Switch #4590

Merged
merged 2 commits into from
Jan 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
---
title: Accessibility
redirect_from:
- /components/switch/accessibility/
---

## Accessibility

The Switch component has been designed with accessibility in mind.

It supports keyboard navigation and includes the following properties that provide additional information to screen readers:

| Name | Type | Description |
| :------------- | :------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| ariaControls | `string` | Allows you to specify an `aria-controls` attribute to establish the relationship between the Switch and element which is controlled by it. `ariaControls` works only with a unique `id` of an element. |
| ariaLabel | `string` | Allows you to specify an `aria-label` attribute of the component. |
| ariaLabelledby | `string` | Allows you to specify an `aria-labelledby` attribute of the component. This attribute references the id(s) of element(s) that label(s) the Switch, separated by a space. The elements with those ids can be hidden, so that their text is only announced by screen readers. |

While these props are optional, we recommend including them to ensure proper functionality with assistive technologies.
This helps users better understand the component's context and improves the overall experience.

### Example

The following code snippet shows how to use these properties:

```jsx
<Card
title="Billing details"
actions={
<Switch
checked={isExpanded}
onChange={handleShowBillingDetails}
ariaLabel="Toggle billing details section"
ariaControls="BillingDetailsForm"
/>
}
>
{isExpanded && (
<CardSection>
<BillingDetailsForm id="BillingDetailsForm" />
</CardSection>
)}
</Card>
```

Using the `ariaLabel` prop enables screen readers to properly announce the Switch component.
Alternatively, you can use the `ariaLabelledby` prop to reference another element that serves as a label for the Switch component.

For enhanced accessibility, it is recommended to have these label strings translated and dynamically updated based on the user's current journey state
(e.g. if a billing details section is expanded and the user is about to collapse it, the screen reader should properly announce it).

The `ariaControls` prop establishes a connection between the Switch and the element it controls.
This relationship enables users to navigate directly from the Switch to this element, improving the overall navigation experience.
24 changes: 13 additions & 11 deletions packages/orbit-components/src/Switch/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,16 @@ After adding import to your project you can use it simply like:

The table below contains all types of the props available in the Switch component.

| Name | Type | Default | Description |
| :------------- | :---------------------- | :----------- | :-------------------------------------- |
| dataTest | `string` | | Optional prop for testing purposes. |
| id | `string` | | Set `id` for `Switch` input |
| **onChange** | `() => void \| Promise` | | Function for handling onChange event. |
| onFocus | `() => void \| Promise` | | Function for handling onFocus event. |
| onBlur | `() => void \| Promise` | | Function for handling onBlur event. |
| **checked** | `boolean` | | If `true`, the Switch will be checked. |
| ariaLabelledby | `string` | | Optional prop for a11y element |
| icon | `React.Node` | `<Circle />` | Optional property for custom icon. |
| disabled | `boolean` | `false` | If `true`, the Switch will be disabled. |
| Name | Type | Default | Description |
| :------------- | :---------------------- | :----------- | :----------------------------------------- |
| dataTest | `string` | | Optional prop for testing purposes. |
| id | `string` | | Set `id` for `Switch` input. |
| **onChange** | `() => void \| Promise` | | Function for handling onChange event. |
| onFocus | `() => void \| Promise` | | Function for handling onFocus event. |
| onBlur | `() => void \| Promise` | | Function for handling onBlur event. |
| **checked** | `boolean` | | If `true`, the Switch will be checked. |
| icon | `React.Node` | `<Circle />` | Optional property for custom icon. |
| disabled | `boolean` | `false` | If `true`, the Switch will be disabled. |
| ariaControls | `string` | | Optional prop for `aria-controls` value. |
| ariaLabel | `string` | | Optional prop for `aria-label` value. |
| ariaLabelledby | `string` | | Optional prop for `aria-labelledby` value. |
sarkaaa marked this conversation as resolved.
Show resolved Hide resolved
3 changes: 2 additions & 1 deletion packages/orbit-components/src/Switch/Switch.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const meta: Meta<typeof Switch> = {
args: {
checked: false,
onChange: action("onChange"),
ariaLabel: "Set up price alerts",
},

parameters: {
Expand All @@ -42,6 +43,7 @@ export const CustomIcon: Story = {
args: {
checked: true,
icon: "Lock",
ariaLabel: "Lock the price",
},

argTypes: {
Expand All @@ -64,7 +66,6 @@ export const Playground: Story = {
args: {
...CustomIcon.args,
id: "switch-id",
ariaLabelledby: "aria-labelledby",
disabled: false,
onFocus: action("onFocus"),
onBlur: action("onBlur"),
Expand Down
21 changes: 19 additions & 2 deletions packages/orbit-components/src/Switch/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,22 @@ import handleKeyDown from "../utils/handleKeyDown";
import type { Props } from "./types";

const Switch = React.forwardRef<HTMLInputElement, Props>(
({ onChange, checked, dataTest, id, icon, onBlur, onFocus, disabled, ariaLabelledby }, ref) => {
(
{
onChange,
checked,
dataTest,
id,
icon,
onBlur,
onFocus,
disabled,
ariaControls,
ariaLabel,
Comment on lines +21 to +22
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added ariaLabel as I believe both ariaLabel and ariaLabelledby should be available.

I added ariaControls as Switch is currently used in multiple places where it controls other elements, e.g. in Search it's used in the Price Alert where switching it on can display Sign In modal for logged out users and when switched off it, it displays the modal. Same for booking where Switch controls the the Billing details Card to be expanded/collapsed. I believe that in these situations aria-controls should be set.

ariaLabelledby,
},
ref,
) => {
return (
<label className="inline-block">
<div
Expand All @@ -28,14 +43,16 @@ const Switch = React.forwardRef<HTMLInputElement, Props>(
disabled={disabled}
domihustinova marked this conversation as resolved.
Show resolved Hide resolved
aria-checked={checked}
role="switch"
aria-labelledby={ariaLabelledby}
onKeyDown={!disabled ? handleKeyDown<HTMLInputElement>(undefined, onChange) : undefined}
onBlur={!disabled ? onBlur : undefined}
onChange={!disabled ? onChange : undefined}
onFocus={!disabled ? onFocus : undefined}
type="checkbox"
data-test={dataTest}
id={id}
aria-controls={ariaControls}
aria-label={ariaLabel}
aria-labelledby={ariaLabelledby}
/>
<div
className={cx(
Expand Down
4 changes: 3 additions & 1 deletion packages/orbit-components/src/Switch/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ import type * as Common from "../common/types";
export interface Props extends Common.Globals {
readonly icon?: React.ReactNode;
readonly checked: boolean;
readonly ariaLabelledby?: string;
readonly disabled?: boolean;
readonly onChange: React.ChangeEventHandler<HTMLInputElement>;
readonly onFocus?: React.FocusEventHandler<HTMLInputElement>;
readonly onBlur?: React.FocusEventHandler<HTMLInputElement>;
readonly ariaControls?: string;
readonly ariaLabel?: string;
readonly ariaLabelledby?: string;
}
Loading