Base components
Switch
BetaSince 0.2.0
Turns one setting on or off, and the change applies at once.
import { Switch } from "@anantya/uix";Overview
Use a switch for one setting that takes effect as soon as it changes, such as email alerts. It is a checkbox underneath, so a <label> names it, and it works with forms and form libraries such as react-hook-form.
For a choice that applies only when a form is submitted, such as agreeing to terms, use a checkbox. For a choice between more than two options, use radio buttons.
Playground
Note
The playground forSwitch is coming soon.Examples
A <label> around it names it, and clicking the label toggles it too.
Code
import { Switch } from "@anantya/uix";
export default function WithLabel() {
return (
<label className="flex items-center gap-3 text-sm">
<Switch defaultChecked />
Email alerts
</label>
);
}Two sizes: md, the default, and sm for dense screens, such as a table toolbar.
Code
import { Switch } from "@anantya/uix";
export default function Sizes() {
return (
<div className="flex flex-col gap-3 text-sm">
<label className="flex items-center gap-3">
<Switch size="sm" defaultChecked />
Compact rows
</label>
<label className="flex items-center gap-3">
<Switch defaultChecked />
Show totals
</label>
</div>
);
}A description under the label, linked with aria-describedby, says what the setting does.
An email every Monday with last week's paid and overdue invoices.
Code
import { Switch } from "@anantya/uix";
export default function WithDescription() {
return (
<div className="flex max-w-sm items-start justify-between gap-6 text-sm">
<div className="flex flex-col gap-1">
<label htmlFor="weekly-summary" className="font-medium">
Weekly summary
</label>
<p id="weekly-summary-description" className="text-muted-foreground">
An email every Monday with last week's paid and overdue invoices.
</p>
</div>
<Switch id="weekly-summary" aria-describedby="weekly-summary-description" />
</div>
);
}With checked and onCheckedChange, your app holds the state and reacts to each change.
You pay each invoice yourself.
Code
"use client";
import { Switch } from "@anantya/uix";
import { useState } from "react";
export default function Controlled() {
const [autoPay, setAutoPay] = useState(false);
return (
<div className="flex flex-col items-center gap-2 text-sm">
<label className="flex items-center gap-3">
<Switch checked={autoPay} onCheckedChange={setAutoPay} />
Auto-pay
</label>
<p className="text-muted-foreground">
{autoPay ? "Invoices are paid on their due date." : "You pay each invoice yourself."}
</p>
</div>
);
}A disabled switch can't change or be reached with Tab, so its description says what turns it on.
Add a phone number to your profile to turn these on.
Code
import { Switch } from "@anantya/uix";
export default function Disabled() {
return (
<div className="flex max-w-sm items-start justify-between gap-6 text-sm">
<div className="flex flex-col gap-1">
<label htmlFor="sms-alerts" className="font-medium">
SMS alerts
</label>
<p id="sms-alerts-description" className="text-muted-foreground">
Add a phone number to your profile to turn these on.
</p>
</div>
<Switch id="sms-alerts" aria-describedby="sms-alerts-description" disabled />
</div>
);
}Props
Note
The props table forSwitch is coming soon.Accessibility
It follows the WAI-ARIA switch pattern, on an <input type="checkbox" role="switch">.
| Key | Action |
|---|---|
| Tab | Moves focus to the next switch. A disabled switch is skipped. |
| Shift + Tab | Moves focus to the previous switch. |
| Space | Turns it on or off. |
Screen readers
- A screen reader announces it as a switch named by its label, and whether it is on or off, such as "Email alerts, switch, on"
- Its
aria-describedbytext, such as a description, is read after its name - A disabled switch is announced as unavailable
Contrast
Its track and thumb need 3:1, as does the focus outline. The lowest contrast on the page and in a dialog:
| Part | Light | Dark |
|---|---|---|
| Track, off | 4.8:1 | 3.6:1 |
| Track, on | 5.2:1 | 3.3:1 |
| Thumb against its track | 4.8:1 | 4.8:1 |
| Focus outline | 5.2:1 | 6.7:1 |
On or off doesn't rely on color alone: the thumb moves to the other end. A disabled switch is exempt from contrast minimums, as WCAG allows for inactive controls. With forced colors on, such as Windows high contrast, it draws in system colors.
WCAG 2.2
- 1.3.1 Info and Relationships: its
<label>names it, andaria-describedbylinks its description - 1.4.1 Use of Color: the thumb's position shows its state
- 1.4.11 Non-text Contrast, in light and dark
- 2.1.1 Keyboard: Space turns it on or off
- 2.4.7 Focus Visible: a focus outline shows on keyboard focus
- 2.5.8 Target Size (Minimum): the pointer reaches 4px past the track, so even
smis at least 24px high - 4.1.2 Name, Role, Value: a switch with its on or off state
axe checks every example on this page in CI, in light and dark, in Chromium, Firefox and WebKit.
What your app still has to do
- Give every switch a visible label, with a
<label>oraria-labelledby - Apply the change as soon as it is made, without a Save button. If saving fails, turn the switch back and tell the reader, such as with
announce("Couldn't turn on email alerts") - Keep the label the same when the state changes; the switch already says on or off
Do / Don't
Name the setting, such as Email alerts.
Don't name the action or change the label with the state, such as Turn off alerts. A screen reader user hears both the label and on or off.
Use a switch for a setting that applies at once.
Don't use one in a form that applies only on submit, such as Accept the terms. Use a checkbox, so the reader doesn't expect an instant change.
Put the label right beside the switch, or on the same row in a settings list.
Don't leave a switch without a label. A screen reader user hears only 'switch, off'.
Changelog
Every change to Switch since 0.2.0 is in the release notes.