A two-state control used to toggle between checked and unchecked states
npx @tessl/cli install tessl/npm-radix-ui--react-switch@1.2.0@radix-ui/react-switch is a TypeScript React component library that provides an accessible, unstyled switch component. It implements a two-state control for toggling between checked and unchecked states with proper ARIA attributes, keyboard navigation, and form integration.
npm install @radix-ui/react-switchimport * as Switch from "@radix-ui/react-switch";Or with named imports:
import { Switch, SwitchThumb, Root, Thumb } from "@radix-ui/react-switch";For CommonJS:
const Switch = require("@radix-ui/react-switch");import * as Switch from "@radix-ui/react-switch";
import { useState } from "react";
function SwitchDemo() {
const [checked, setChecked] = useState(false);
return (
<form>
<div style={{ display: "flex", alignItems: "center" }}>
<label
className="Label"
htmlFor="airplane-mode"
style={{ paddingRight: 15 }}
>
Airplane mode
</label>
<Switch.Root
className="SwitchRoot"
id="airplane-mode"
checked={checked}
onCheckedChange={setChecked}
>
<Switch.Thumb className="SwitchThumb" />
</Switch.Root>
</div>
</form>
);
}The component follows Radix UI's compound component pattern with two main parts:
The component supports both controlled and uncontrolled usage patterns, integrates seamlessly with HTML forms, and provides proper ARIA attributes for screen readers.
The main switch component that renders as an accessible button with switch role.
/**
* Base primitive components from @radix-ui/react-primitive
*/
type PrimitivePropsWithRef<E extends React.ElementType> = React.ComponentPropsWithRef<E> & {
asChild?: boolean;
};
interface PrimitiveForwardRefComponent<E extends React.ElementType>
extends React.ForwardRefExoticComponent<PrimitivePropsWithRef<E>> {}
declare const Primitive: {
button: PrimitiveForwardRefComponent<"button">;
span: PrimitiveForwardRefComponent<"span">;
input: PrimitiveForwardRefComponent<"input">;
};
/**
* Element type for Switch component
*/
type SwitchElement = React.ComponentRef<typeof Primitive.button>;
/**
* Props type for Primitive.button
*/
type PrimitiveButtonProps = React.ComponentPropsWithoutRef<typeof Primitive.button>;
/**
* Main switch component that renders as a button with switch role
*/
const Switch: React.ForwardRefExoticComponent<
SwitchProps & React.RefAttributes<SwitchElement>
>;
/**
* Alternative export name for the Switch component
*/
const Root: React.ForwardRefExoticComponent<
SwitchProps & React.RefAttributes<SwitchElement>
>;
interface SwitchProps extends PrimitiveButtonProps {
/** The controlled checked state of the switch */
checked?: boolean;
/** The checked state of the switch when it is initially rendered. Use when you do not need to control its checked state */
defaultChecked?: boolean;
/** When true, indicates that the user must check the switch before the owning form can be submitted */
required?: boolean;
/** The name of the switch. Submitted with its owning form as part of a name/value pair */
name?: string;
/** The value given as data when submitted with a `name`. Defaults to "on" */
value?: string;
/** Associates the control with a form element */
form?: string;
/** When true, prevents the user from interacting with the switch */
disabled?: boolean;
/** Event handler called when the checked state of the switch changes */
onCheckedChange?(checked: boolean): void;
}Key Features:
checked + onCheckedChange) and uncontrolled (defaultChecked) patternsname and value propsrole="switch", aria-checked, aria-required)SwitchBubbleInput component for seamless form submissionData Attributes:
data-state: "checked" | "unchecked" - Current switch statedata-disabled: Present when disabledThe visual indicator component that displays the current state of the switch.
/**
* Element type for SwitchThumb component
*/
type SwitchThumbElement = React.ComponentRef<typeof Primitive.span>;
/**
* Props type for Primitive.span
*/
type PrimitiveSpanProps = React.ComponentPropsWithoutRef<typeof Primitive.span>;
/**
* Visual thumb/handle component that indicates the switch state
*/
const SwitchThumb: React.ForwardRefExoticComponent<
SwitchThumbProps & React.RefAttributes<SwitchThumbElement>
>;
/**
* Alternative export name for the SwitchThumb component
*/
const Thumb: React.ForwardRefExoticComponent<
SwitchThumbProps & React.RefAttributes<SwitchThumbElement>
>;
interface SwitchThumbProps extends PrimitiveSpanProps {}Key Features:
Data Attributes:
data-state: "checked" | "unchecked" - Current switch statedata-disabled: Present when disabledUtility function for creating component scopes when composing multiple switch instances.
/**
* Scope type for context management
*/
type Scope<C = any> = { [scopeName: string]: React.Context<C>[] } | undefined;
/**
* Hook type returned by createSwitchScope
*/
type ScopeHook = (scope: Scope) => { [__scopeSwitch: string]: Scope };
/**
* Creates a scope for switch components to avoid context conflicts
* Used when composing multiple switch instances or building compound components
*/
function createSwitchScope(): ScopeHook;Usage:
import { createSwitchScope } from "@radix-ui/react-switch";
// Create a scoped context for this specific switch instance
const useScope = createSwitchScope();
function MyComponent() {
const scope = useScope();
return (
<Switch.Root {...scope}>
<Switch.Thumb />
</Switch.Root>
);
}import * as Switch from "@radix-ui/react-switch";
import { useState } from "react";
function ControlledSwitch() {
const [isChecked, setIsChecked] = useState(false);
return (
<Switch.Root checked={isChecked} onCheckedChange={setIsChecked}>
<Switch.Thumb />
</Switch.Root>
);
}import * as Switch from "@radix-ui/react-switch";
function UncontrolledSwitch() {
return (
<Switch.Root defaultChecked={true}>
<Switch.Thumb />
</Switch.Root>
);
}import * as Switch from "@radix-ui/react-switch";
function FormSwitch() {
return (
<form onSubmit={(e) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
console.log(formData.get("notifications")); // "on" or null
}}>
<div style={{ display: "flex", alignItems: "center" }}>
<label htmlFor="notifications-switch" style={{ paddingRight: 15 }}>
Enable notifications
</label>
<Switch.Root
id="notifications-switch"
name="notifications"
value="on"
defaultChecked={false}
>
<Switch.Thumb />
</Switch.Root>
</div>
<button type="submit">Submit</button>
</form>
);
}Form Integration Details:
name prop determines the form field name in the submitted datavalue prop sets the value when checked (defaults to "on")import * as Switch from "@radix-ui/react-switch";
function DisabledSwitch() {
return (
<div style={{ display: "flex", alignItems: "center" }}>
<label htmlFor="disabled-switch" style={{ paddingRight: 15 }}>
Cannot change this
</label>
<Switch.Root id="disabled-switch" disabled defaultChecked={true}>
<Switch.Thumb />
</Switch.Root>
</div>
);
}import * as Switch from "@radix-ui/react-switch";
import "./switch.css"; // Your custom styles
function StyledSwitch() {
return (
<Switch.Root className="switch-root">
<Switch.Thumb className="switch-thumb" />
</Switch.Root>
);
}Example CSS:
.switch-root {
width: 42px;
height: 25px;
background-color: gray;
border-radius: 9999px;
position: relative;
border: none;
cursor: pointer;
}
.switch-root[data-state="checked"] {
background-color: blue;
}
.switch-root[data-disabled] {
opacity: 0.5;
cursor: not-allowed;
}
.switch-thumb {
display: block;
width: 21px;
height: 21px;
background-color: white;
border-radius: 9999px;
transition: transform 100ms;
transform: translateX(2px);
will-change: transform;
}
.switch-thumb[data-state="checked"] {
transform: translateX(19px);
}
.switch-thumb[data-disabled] {
opacity: 0.8;
}role="switch", aria-checked, and aria-required attributes