Persona

Toggle

Inputs · 12 props · Updated Sep 22, 2026

Toggle is an on/off switch. The switch you see is one element; the control a screen reader meets is a hidden checkbox with role="switch" beside it. It is fully controlled, so the thumb moves when you update value and not when the reader presses it. ToggleField is the one to render inside a form layout, though it buys less than the other composed fields do.

  • Label (optional) what the switch controls. Set labelOn and labelOff where the wording should change with the state.
  • Switch the control. Track and thumb are one element: PDS paints both as pseudo-elements on it, so the thumb has no box of its own and its position is the state.

The label sits to the left of the switch, which is the opposite side from Checkbox and Radio. labelOn and labelOff take a label each, for the cases where the wording has to change with the state, and labelOff falls back to labelOn, so a single label is the ordinary case, and it is part of the target: clicking the label throws the switch. The thumb slides between the two ends, and that slide is the only motion in the control.

A toggle takes effect as it is switched, and there is nothing between the press and the consequence: no submit step, no confirmation, no undo. The setting behind one has to be safe to throw and safe to throw back.

The switch keeps its own label even inside a field

Section titled “The switch keeps its own label even inside a field”

ToggleField is a toggle in a FormField, which gets the switch a label above it, helper text, warning text and error text in the same fixed order every other field uses, and rightActions beside the label. Render it wherever a toggle sits in a form among other fields, so the switch lines up with them.

What it does not do is connect the two. Toggle generates its own input id and ignores the one the field passes down, so the field’s label is a heading over the switch rather than its accessible name — the switch is still named by labelOn, labelOff or aria-label. The same is true of Slider, List Box and Swatch Picker, and it is why the two label props are worth keeping even when the field draws a label.

disabled is the one thing that does travel: a ToggleField marked disabled disables the switch through context. Because ToggleField forwards every other field prop untouched, there is no toggle-specific reason to compose a FormField by hand — only the general one, which is a field slot holding more than the switch.

Toggle submits nothing, because its hidden input carries no name and no value. A toggle inside a <form> contributes nothing to what the form posts, and its state exists only in the variable you passed to value. Where the answer should travel with the form, the control is a Checkbox. Where the reader is choosing between two named alternatives rather than turning one thing on, it is Radio.

Confirm a change with a button, not a switch

Section titled “Confirm a change with a button, not a switch”

There is no pending state and no confirm step: onChange fires on the press, and the next thing the reader sees is the consequence. A change worth confirming needs a button and a Modal instead. A toggle also stays where it is put, so a mode that ought to expire on its own is not one.

The parent owns the value, and anything falsy is off

Section titled “The parent owns the value, and anything falsy is off”

The parent owns the state. onChange hands you the next boolean and that is all Toggle does with it: the hidden input is read-only and the switch renders from value, so a parent that does not store the new value leaves the reader pressing a switch that will not move. There is no uncontrolled mode.

value is required and deliberately loose: false, null, undefined and the empty string all count as off. A value that has not loaded yet therefore renders as a switch in the off position rather than as nothing, which is worth knowing before you show a toggle for a setting you are still fetching.

Every toggle carries a title on its root element, defaulting to “On” and “Off”, so hovering one for a moment produces a browser tooltip naming the state. titleOn and titleOff replace those strings, and passing an empty string removes the tooltip. Replace them or remove them: a tooltip reading “On” repeats what the thumb’s position already said, and spends the one gesture that could have explained the setting.

With labelOn or labelOff, the rendered label carries a for attribute pointing at the hidden input, and that is what names it. With no label and no aria-label, Toggle falls back to aria-label="Toggle on" or "Toggle off", which is the control’s own mechanism and its current state and not one word about what is being switched. Set aria-label on any toggle without a visible label — including one whose only label comes from a ToggleField — and name what it controls.

Check where a disabled toggle was disabled

Section titled “Check where a disabled toggle was disabled”

disabled disables the hidden input, which takes the toggle out of the tab order altogether: a keyboard reader never reaches it, and the muted styling is the whole of the notice they get. It can also arrive from an enclosing Form Field instead of from the prop, so a toggle can be unreachable without anything in its own props saying so. Where that is deliberate, the reason belongs in the text beside it.

Name what is switched, and keep the label true in both positions

Section titled “Name what is switched, and keep the label true in both positions”

“Email notifications” reads correctly whichever way the switch is thrown. “Don’t send email” does not: switched on it means email is off, and the reader has to hold two negations at once to work out what they have just done. Let the thumb’s position carry on and off, and let the label carry the subject.

Where the wording genuinely has to change, give labelOn and labelOff wordings that both still name the setting. Sentence case and punctuation are on Writing.

ToggleField is every prop below plus the twelve FormField carries:

NameType
label

ReactNode

Renders <Label> above input

inputId

string

ID of the input element. Providing this will automatically link the label to the input.

helperText

ReactNode

Renders <HelperText> below input

errorText

ReactNode

Renders <ErrorText> below input and helper text.

warningText

ReactNode

Renders <WarningText> below input and helper text.

labelProps

FormLabelProps

helperProps

(HelperTextProps & RefAttributes<HTMLDivElement>)

warningProps

(Omit<DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>, “ref”> & RefAttributes<HTMLDivElement>)

errorProps

(Omit<DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>, “ref”> & RefAttributes<HTMLDivElement>)

configField

boolean

Whether to render the form field as a config field. This will render the helper text as a tooltip.

configFieldInputWidth

string

Override the width of the input area when configField is true. Accepts any valid CSS width value (e.g. ‘33%’, ‘66.67%’). Defaults to ‘50%‘.

rightActions

ReactNode

Renders actions to the right of the label.

className

string

disabled

boolean

Disables interaction and renders the toggle in a muted style.

titleOff

string

Tooltip / title shown on the root element when the toggle is off.

titleOn

string

Tooltip / title shown on the root element when the toggle is on.

onChange

((value: boolean) => void)

Called with the new boolean value when the user clicks the toggle. The component is fully controlled — you must update value in response.

valuerequired

boolean, null,

Current on/off state. Falsy values (false, null, undefined, ) are treated as off; true is treated as on.

id

string

id forwarded to the visible switch element.

labelOn

ReactNode

Label node rendered to the left of the switch when the toggle is on.

labelOff

ReactNode

Label node rendered to the left of the switch when the toggle is off. Falls back to labelOn when not provided.

data-test

string

aria-label

string

Accessible label for the hidden <input role=“switch”>. Required when no visible label is provided via labelOn / labelOff.

size

ToggleSize

Size of the toggle switch.

Toggle’s own props, which ToggleField also accepts:

NameType
className

string

disabled

boolean

Disables interaction and renders the toggle in a muted style.

titleOff

string

Tooltip / title shown on the root element when the toggle is off.

titleOn

string

Tooltip / title shown on the root element when the toggle is on.

onChange

((value: boolean) => void)

Called with the new boolean value when the user clicks the toggle. The component is fully controlled — you must update value in response.

valuerequired

boolean, null,

Current on/off state. Falsy values (false, null, undefined, ) are treated as off; true is treated as on.

id

string

id forwarded to the visible switch element.

labelOn

ReactNode

Label node rendered to the left of the switch when the toggle is on.

labelOff

ReactNode

Label node rendered to the left of the switch when the toggle is off. Falls back to labelOn when not provided.

data-test

string

aria-label

string

Accessible label for the hidden <input role=“switch”>. Required when no visible label is provided via labelOn / labelOff.

size

ToggleSize

Size of the toggle switch.

Was this page helpful?