Toggle (Switch)
A toggle is used to switch between two possible states, commonly enabled or disabled states.
import { Toggle } from "@opengovsg/oui"export const Example = () => { return <Toggle>Toggle</Toggle>}Usage
import { Toggle } from "@opengovsg/oui"<Toggle>Enable notifications</Toggle>Alternatively, install the component as local source via the shadcn CLI:
npx shadcn@latest add https://oui.open.gov.sg/r/toggle.jsonpnpm dlx shadcn@latest add https://oui.open.gov.sg/r/toggle.jsonnpx shadcn@latest add https://oui.open.gov.sg/r/toggle.jsonbunx --bun shadcn@latest add https://oui.open.gov.sg/r/toggle.jsonThe Toggle component allows users to switch between two states (on/off). It is built on React Aria's Switch.
If the component does not have a visible label (by passing children), an aria-label prop must be passed instead to identify it to assistive technology.
Examples
With Label
Pass children to add a visible text label next to the toggle.
import { Toggle } from "@opengovsg/oui"export const Example = () => { return <Toggle>Enable notifications</Toggle>}Label Placement
Use the labelPlacement prop to position the label before or after the toggle. Defaults to "end".
import { Toggle } from "@opengovsg/oui"export const Example = () => { return ( <div className="flex flex-col gap-4"> <Toggle labelPlacement="end">Label at end (default)</Toggle> <Toggle labelPlacement="start">Label at start</Toggle> </div> )}Sizes
Use the size prop to change the size of the toggle. Defaults to "md".
import { Toggle } from "@opengovsg/oui"export const Example = () => { return ( <div className="flex flex-col gap-4"> <Toggle size="xs">Extra small</Toggle> <Toggle size="sm">Small</Toggle> <Toggle size="md">Medium (default)</Toggle> </div> )}Thumb Icon
Use the thumbIcon prop to display an icon inside the toggle thumb. The prop accepts a React element or a render function that receives SwitchRenderProps (including isSelected and className).
import { Check, X } from "lucide-react"import { Toggle } from "@opengovsg/oui"export const Example = () => { return ( <Toggle aria-label="Toggle" thumbIcon={({ isSelected, className }) => isSelected ? ( <Check className={className} /> ) : ( <X className={className} /> ) } /> )}Disabled
Use the isDisabled prop to disable the toggle.
import { Toggle } from "@opengovsg/oui"export const Example = () => { return ( <div className="flex flex-col gap-4"> <Toggle isDisabled>Disabled (off)</Toggle> <Toggle isDisabled defaultSelected> Disabled (on) </Toggle> </div> )}Controlled
Use the isSelected and onChange props to control the toggle state programmatically.
import { useState } from "react"import { Toggle } from "@opengovsg/oui"export const Example = () => { const [isSelected, setIsSelected] = useState(false) return ( <div className="flex flex-col gap-2"> <Toggle isSelected={isSelected} onChange={setIsSelected}> Dark mode </Toggle> <p className="text-base-content-medium text-sm"> Dark mode is {isSelected ? "on" : "off"}. </p> </div> )}Events
Toggle supports the following event handlers, inherited from React Aria's Switch:
onChange— Called when the selection state changes, receiving a boolean.onFocus/onBlur— Called when the toggle gains or loses focus.onFocusChange— Called when the focus state changes, receiving a boolean.onKeyDown/onKeyUp— Called on keyboard events.onHoverStart/onHoverEnd/onHoverChange— Called on hover events.
Accessibility
- Built on the WAI-ARIA switch role, providing correct semantics for assistive technology.
- Keyboard interaction: Press Space to toggle the switch state.
- Label is automatically associated with the switch input.
- Disabled and selected states are exposed to assistive technology.
- If no visible label is provided via
children, anaria-labelprop must be set.
Slots
- base: The root container wrapper with label.
- track: The toggle track (background).
- thumb: The toggle thumb (sliding circle).
- thumbIcon: The icon inside the thumb.
Custom Styles
You can customize the Toggle component by passing custom Tailwind CSS classes to the component slots via the classNames prop.
import { Toggle } from "@opengovsg/oui"export const Example = () => { return ( <Toggle defaultSelected classNames={{ base: "gap-4 rounded-lg bg-blue-50 px-4 py-3", track: "rounded-sm bg-blue-200 in-selected:bg-blue-600", thumb: "rounded-sm border-0 shadow-md", }} > Squared toggle </Toggle> )}Props
Toggle
| Prop | Type | Default | Description |
|---|---|---|---|
children | React.ReactNode | - | The label content. Required if aria-label is not set. |
aria-label | string | - | Accessible label. Required if children is not set. |
isSelected | boolean | - | The current selection state (controlled) |
defaultSelected | boolean | - | The default selection state (uncontrolled) |
onChange | (isSelected: boolean) => void | - | Callback fired when the selection state changes |
thumbIcon | React.ReactElement | ((props: ToggleThumbIconProps) => React.ReactNode) | - | Icon to display inside the thumb. Render function receives isSelected and className. |
labelPlacement | "start" | "end" | "end" | Where to place the label relative to the toggle |
size | "xs" | "sm" | "md" | "md" | The size of the toggle |
colorScheme | "success" | "success" | The color scheme of the toggle |
isDisabled | boolean | false | Whether the toggle is disabled |
isReadOnly | boolean | false | Whether the toggle is read-only |
name | string | - | The name of the input, used when submitting a form |
value | string | - | The value of the input, used when submitting a form |
autoFocus | boolean | false | Whether the toggle should receive focus on render |
onFocus | (e: FocusEvent) => void | - | Called when the toggle receives focus |
onBlur | (e: FocusEvent) => void | - | Called when the toggle loses focus |
onFocusChange | (isFocused: boolean) => void | - | Called when the focus state changes |
onKeyDown | (e: KeyboardEvent) => void | - | Called when a key is pressed |
onKeyUp | (e: KeyboardEvent) => void | - | Called when a key is released |
classNames | SlotsToClasses<ToggleSlots> | - | Custom CSS classes for component slots |