Switch (Toggle)
Switches are a pleasant interface for toggling a value between two states, and offer the same semantics and keyboard navigation as native checkbox elements.
To get started, install Headless UI via npm:
npm install @headlessui/react
Switches are built using the Switch
component. You can toggle your Switch by clicking directly on the component, or by pressing the spacebar while its focused.
Toggling the switch calls the onChange
function with a negated version of the checked
value.
import { useState } from 'react' import { Switch } from '@headlessui/react' function MyToggle() { const [enabled, setEnabled] = useState(false) return ( <Switch checked={enabled} onChange={setEnabled} className={`${ enabled ? 'bg-blue-600' : 'bg-gray-200' } relative inline-flex h-6 w-11 items-center rounded-full`} > <span className="sr-only">Enable notifications</span> <span className={`${ enabled ? 'translate-x-6' : 'translate-x-1' } inline-block h-4 w-4 transform rounded-full bg-white transition`} /> </Switch> ) }
Headless UI keeps track of a lot of state about each component, like which switch option is currently selected, whether a popover is open or closed, or which item in a menu is currently active via the keyboard.
But because the components are headless and completely unstyled out of the box, you can't see this information in your UI until you provide the styles you want for each state yourself.
Each component exposes information about its current state via render props that you can use to conditionally apply different styles or render different content.
For example, the Switch
component exposes an checked
state, which tells you if the switch is currently checked or not.
import { useState, Fragment } from 'react' import { Switch } from '@headlessui/react' function MyToggle() { const [enabled, setEnabled] = useState(false) return ( <Switch checked={enabled} onChange={setEnabled} as={Fragment}>
{({ checked }) => (/* Use the `checked` state to conditionally style the button. */ <button className={`${checked ? 'bg-blue-600' : 'bg-gray-200'} relative inline-flex h-6 w-11 items-center rounded-full`} > <span className="sr-only">Enable notifications</span> <span className={`${checked ? 'translate-x-6' : 'translate-x-1'} inline-block h-4 w-4 transform rounded-full bg-white transition`} /> </button> )} </Switch> ) }
For the complete render prop API for each component, see the component API documentation.
Each component also exposes information about its current state via a data-headlessui-state
attribute that you can use to conditionally apply different styles.
When any of the states in the render prop API are true
, they will be listed in this attribute as space-separated strings so you can target them with a CSS attribute selector in the form [attr~=value]
.
For example, here's what the Switch
component renders when the switch is checked:
<!-- Rendered `Switch` --> <button data-headlessui-state="checked"></button>
If you are using Tailwind CSS, you can use the @headlessui/tailwindcss plugin to target this attribute with modifiers like ui-checked:*
:
import { useState } from 'react' import { Switch } from '@headlessui/react' function MyToggle() { const [enabled, setEnabled] = useState(false) return ( <Switch checked={enabled} onChange={setEnabled}
className="relative inline-flex h-6 w-11 items-center rounded-full ui-checked:bg-blue-600 ui-not-checked:bg-gray-200"> <span className="sr-only">Enable notifications</span><span className="inline-block h-4 w-4 transform rounded-full bg-white transition ui-checked:translate-x-6 ui-not-checked:translate-x-1" /></Switch> ) }
By default, a Switch renders a button
as well as whatever children you pass into it. This can make it harder to implement certain UIs, since the children will be nested within the button.
In these situations, you can use the Switch.Label
component for more flexibility.
This example demonstrates how to use the Switch.Group
, Switch
and Switch.Label
components to render a label as a sibling to the button. Note that Switch.Label
works alongside a Switch
component, and they both must be rendered within a parent Switch.Group
component.
import { useState } from 'react' import { Switch } from '@headlessui/react' function MyToggle() { const [enabled, setEnabled] = useState(false) return (
<Switch.Group><div className="flex items-center"><Switch.Label className="mr-4">Enable notifications</Switch.Label><Switch checked={enabled} onChange={setEnabled} className={`${ enabled ? 'bg-blue-600' : 'bg-gray-200' } relative inline-flex h-6 w-11 items-center rounded-full transition-colors focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2`} > <span className={`${ enabled ? 'translate-x-6' : 'translate-x-1' } inline-block h-4 w-4 transform rounded-full bg-white transition-transform`} /> </Switch> </div></Switch.Group>) }
By default, clicking a Switch.Label
will toggle the Switch, just like labels in native HTML checkboxes do. If you'd like to make the label non-clickable (which you might if it doesn't make sense for your design), you can add a passive
prop to the Switch.Label
component:
import { useState } from 'react' import { Switch } from '@headlessui/react' function MyToggle() { const [enabled, setEnabled] = useState(false) return ( <Switch.Group>
<Switch.Label passive>Enable notifications</Switch.Label><Switch checked={enabled} onChange={setEnabled}> {/* ... */} </Switch> </Switch.Group> ) }
If you add the name
prop to your switch, a hidden input
element will be rendered and kept in sync with the switch state.
import { useState } from 'react' import { Switch } from '@headlessui/react' function Example() { const [enabled, setEnabled] = useState(true) return ( <form action="/notification-settings" method="post">
<Switch checked={enabled} onChange={setEnabled} name="notifications">{/* ... */} </Switch> <button>Submit</button> </form> ) }
This lets you use a switch inside a native HTML <form>
and make traditional form submissions as if your switch was a native HTML form control.
By default, the value will be 'on'
when the switch is checked, and not present when the switch is unchecked.
<input type="hidden" name="notifications" value="on" />
You can customize the value if needed by using the value
prop:
import { useState } from 'react' import { Switch } from '@headlessui/react' function Example() { const [enabled, setEnabled] = useState(true) return ( <form action="/accounts" method="post"> <Switch checked={enabled} onChange={setEnabled}
name="terms"value="accept"> {/* ... */} </Switch> <button>Submit</button> </form> ) }
The hidden input will then use your custom value when the switch is checked:
<input type="hidden" name="terms" value="accept" />
If you provide a defaultChecked
prop to the Switch
instead of a checked
prop, Headless UI will track its state internally for you, allowing you to use it as an uncontrolled component.
You can access the current state via the checked
render prop on the Switch
component.
import { Fragment } from 'react' import { Switch } from '@headlessui/react' function Example() { return ( <form action="/accounts" method="post">
<Switch name="terms-of-service" defaultChecked={true} as={Fragment}>{({ checked }) => ( <button className={`${ checked ? 'bg-blue-600' : 'bg-gray-200' } relative inline-flex h-6 w-11 items-center rounded-full`} > <span className="sr-only">Enable notifications</span> <span className={`${ checked ? 'translate-x-6' : 'translate-x-1' } inline-block h-4 w-4 transform rounded-full bg-white transition`} /> </button> )} </Switch> <button>Submit</button> </form> ) }
This can simplify your code when using the listbox with HTML forms or with form APIs that collect their state using FormData instead of tracking it using React state.
Any onChange
prop you provide will still be called when the component's value changes in case you need to run any side effects, but you won't need to use it to track the component's state yourself.
Because Switches are typically always rendered to the DOM (rather than being mounted/unmounted like other components), simple CSS transitions are often enough to animate your Switch:
import { useState } from "react"; import { Switch } from "@headlessui/react"; function MyToggle() { const [enabled, setEnabled] = useState(false); return ( <Switch checked={enabled} onChange={setEnabled}> <span /* Transition the switch's knob on state change */
className={`transform transition ease-in-out duration-200${enabled ? "translate-x-9" : "translate-x-0"}`}/> {/* ... */} </Switch> ); }
Because they're renderless, Headless UI components also compose well with other animation libraries in the React ecosystem like Framer Motion and React Spring.
By default, the children of a Switch
will be used as the label for screen readers. If you're using Switch.Label
, the content of your Switch
component will be ignored by assistive technologies.
Clicking a Switch
or a Switch.Label
toggles the Switch on and off.
Command | Description |
Space when a | Toggles the Switch |
Enter when in a form | Submits the form |
Prop | Default | Description |
as | button | String | Component The element or component the |
checked | — | Boolean Whether or not the switch is checked. |
defaultChecked | — | T The default checked value when using as an uncontrolled component. |
onChange | — | (value: Boolean) => void The function to call when the switch is toggled. |
name | — | String The name used when using this component inside a form. |
value | — | String The value used when using this component inside a form, if it is checked. |
Render Prop | Description |
checked |
Whether or not the switch is checked. |
Prop | Default | Description |
as | label | String | Component The element or component the |
passive | false | Boolean When true, clicking the label won't toggle the |
Prop | Default | Description |
as | p | String | Component The element or component the |
Prop | Default | Description |
as | Fragment | String | Component The element or component the |
If you're interested in predesigned component examples using Headless UI and Tailwind CSS, check out Tailwind UI — a collection of beautifully designed and expertly crafted components built by us.
It's a great way to support our work on open-source projects like this and makes it possible for us to improve them and keep them well-maintained.