Skip to main content

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 for Switch 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 for Switch is coming soon.

Accessibility

It follows the WAI-ARIA switch pattern, on an <input type="checkbox" role="switch">.

KeyAction
TabMoves focus to the next switch. A disabled switch is skipped.
Shift + TabMoves focus to the previous switch.
SpaceTurns 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-describedby text, 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:

PartLightDark
Track, off4.8:13.6:1
Track, on5.2:13.3:1
Thumb against its track4.8:14.8:1
Focus outline5.2:16.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, and aria-describedby links 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 sm is 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> or aria-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.

© Anantya TechLabs. All rights reserved.