Switch
An on/off toggle for settings that apply immediately. Works controlled or uncontrolled.
stableSwitch.tsx
Examples
Basic
Email alerts
SMS alerts
Managed by admin
switch/basic.tsx
import { Switch } from "@streamui/react";
export default function SwitchBasic() {
return (
<div className="flex w-full max-w-xs flex-col gap-5">
<div className="flex items-center justify-between gap-4">
<span id="switch-email" className="font-semibold">
Email alerts
</span>
<Switch aria-labelledby="switch-email" defaultChecked />
</div>
<div className="flex items-center justify-between gap-4">
<span id="switch-sms" className="font-semibold">
SMS alerts
</span>
<Switch aria-labelledby="switch-sms" />
</div>
<div className="flex items-center justify-between gap-4">
<span id="switch-locked" className="font-semibold text-ink-muted">
Managed by admin
</span>
<Switch aria-labelledby="switch-locked" defaultChecked disabled />
</div>
</div>
);
}Controlled
Pass checked and onCheckedChange to own the state.
Maintenance mode
state: off
switch/controlled.tsx
"use client";
import { useState } from "react";
import { Switch } from "@streamui/react";
export default function SwitchControlled() {
const [enabled, setEnabled] = useState(false);
return (
<div className="flex items-center gap-4">
<Switch
aria-labelledby="switch-maintenance"
checked={enabled}
onCheckedChange={setEnabled}
/>
<div>
<p id="switch-maintenance" className="font-semibold">
Maintenance mode
</p>
<p className="font-mono text-xs text-ink-muted">
state: {enabled ? "on" : "off"}
</p>
</div>
</div>
);
}Installation
bun add @streamui/reactusage.tsx
import { Switch } from "@streamui/react";
<span id="alerts">Email alerts</span>
<Switch aria-labelledby="alerts" defaultChecked />API
| Prop | Type | Default | Description |
|---|---|---|---|
| checked | boolean | — | Controlled state. Omit to let the switch manage itself. |
| defaultChecked | boolean | false | Initial state when uncontrolled. |
| onCheckedChange | (checked: boolean) => void | — | Called with the new state on every toggle. |
| disabled | boolean | false | Blocks interaction. |
| ...props | ComponentProps<"button"> | — | Native button attributes, e.g. aria-labelledby. |
Accessibility
- Uses role="switch" with aria-checked, so screen readers announce "on" or "off".
- Needs an accessible name: aria-labelledby pointing at visible text, or aria-label.
- Space and Enter toggle it, like a native button.
Source
packages/react/src/Switch.tsx
"use client";
import { useState, type ComponentProps } from "react";
import { cn } from "./cn";
export type SwitchProps = Omit<ComponentProps<"button">, "onChange"> & {
checked?: boolean;
defaultChecked?: boolean;
onCheckedChange?: (checked: boolean) => void;
};
export function Switch({
checked,
defaultChecked = false,
onCheckedChange,
className,
...props
}: SwitchProps) {
const [internal, setInternal] = useState(defaultChecked);
const isOn = checked ?? internal;
return (
<button
type="button"
role="switch"
aria-checked={isOn}
onClick={() => {
setInternal(!isOn);
onCheckedChange?.(!isOn);
}}
className={cn(
"relative inline-flex h-8 w-14 shrink-0 cursor-pointer items-center border-2 border-ink shadow-brutal-sm transition-colors duration-150 motion-reduce:transition-none",
"disabled:pointer-events-none disabled:opacity-50",
isOn ? "bg-lime" : "bg-paper-sunken",
className,
)}
{...props}
>
<span
aria-hidden
className={cn(
"size-5 border-2 border-ink bg-paper-raised transition-[translate] duration-150 ease-out motion-reduce:transition-none",
isOn ? "translate-x-7" : "translate-x-1",
)}
/>
</button>
);
}