KKUONIDesign System

Components

Toggle

Beta

A switch control for binary on/off settings that take immediate effect — no form submission required.

Email me new Maldives deals

Purpose

The Toggle is a switch control that turns a single setting on or off immediately — the change takes effect the moment the user activates it, without a form submission or confirmation step. It renders with role="switch" and communicates its current state through aria-checked.

Usage

Use a Toggle when the action takes effect instantly (preference settings, notification toggles, feature flags), there is a single binary setting the user flips back and forth, and the UI needs to communicate the current state clearly at a glance.

Do not use a Toggle when the change requires a Save or Submit action to persist — use a Checkbox instead. When multiple options need to be selected from a set, use Checkboxes; when a single option must be chosen from several, use a Radio Button group. Avoid Toggle for destructive or irreversible actions — its instant nature is inappropriate for high-consequence actions.

Toggle typically appears in account and notification preference screens, settings lists, and inline feature-flag controls.

Anatomy

Price alerts for Santorini
Holiday newsletter
  1. 1
    TrackPill-shaped container (rounded-full). OFF state: grey-300 background. ON state: teal (#005B55) background. 200 ms colour transition.
  2. 2
    ThumbWhite circular button that slides left (OFF) or right (ON). The 200 ms translateX transition communicates the state change smoothly.
  3. 3
    LabelOptional visible text. Positioned to the left or right of the track via the labelPosition prop. Clicking the label activates the toggle.
  4. 4
    DescriptionOptional secondary text below the label. Provides context without extending the label.
  5. 5
    Focus ring2 px teal ring with 2 px offset on the track element. Visible on keyboard focus only (focus-visible).

Variants

Default (medium, label right)

The medium size with the label on the right is the most common configuration. Use it for preference screens, account settings, and notification controls.

Show prices in GBP
Show prices in EUR

Small size

The size="sm" toggle (20×36 px thumb) is appropriate for compact settings panels, inline table rows, or toolbars where the medium size would feel too heavy.

Compact view
Show sold-out hotels
SizeTrack dimensionsThumb diameterUse when
sm36×20 px16 pxCompact panels, inline table cells, toolbars
md44×24 px20 pxGeneral settings, preference screens

Label left

labelPosition="left" places the label before the track. Use when right-aligning the toggle in a settings list where labels sit on the left and controls on the right.

Travel insurance reminders
Last-minute deal alerts

Without a label

When the toggle sits adjacent to a self-describing element (for example a settings row with a heading and paragraph), you can omit the label and supply an aria-label directly for screen readers.

Price drop alerts

Notify me when holiday prices fall.


With description

The description prop adds secondary text beneath the label, giving context for less-obvious settings without adding it to the label itself.

Wishlist sharingAnyone with the link can view your saved holidays.
Price lock notificationsWe will email you 48 hours before your price lock expires.

Settings list pattern

The most common pattern is a vertical list of toggles with label-left alignment, used for account or notification preferences. Use labelPosition="left" and a consistent container width so the tracks align on the right.

New destination offers
Price drop alerts
Booking reminders
Travel inspiration

States

Off and on

The OFF state uses a bg-grey-300 track with the thumb translated to the left. The ON state uses a bg-teal track with the thumb translated to the right. Both the colour and the thumb position change together so the state is never communicated by colour alone.

Off state
On state

Focus

The focus ring (2 px teal, 2 px offset) surrounds the track. It is visible only on :focus-visible — keyboard and sequential focus — not on mouse click. The ring colour (#005B55) contrasts at above 3:1 against white backgrounds.


Disabled

The entire control renders at reduced opacity with cursor-not-allowed. The underlying button element has disabled applied, removing it from the tab order and preventing all interaction.

Two-factor authentication
Marketing emails (managed by your company)

Use disabled when:

  • A setting is managed externally (for example by a corporate account)
  • A prerequisite step must be completed first

When a disabled toggle has context the user should know, pair it with help text or a tooltip explaining why.

Best practices

Do

Use Toggle for settings that take effect immediately, such as notification preferences and display options.

Instant effect means no Save needed — the on/off visual communicates the persisted state.

Don't

Use Toggle inside a form that requires a Submit action to save the setting.

If the user must save the form for the change to persist, a Checkbox is the correct control.

Do

Write toggle labels as a noun phrase that names the feature: "Price drop alerts", "Email notifications".

Verb-noun labels like these name the thing being toggled without describing the state.

Don't

Label the toggle with the current state: "Enabled", "Active", "On". The track itself communicates on/off.

State labels like these become confusing when flipped — 'Enabled' on an off toggle reads as contradictory.

Do

Use labelPosition="left" in settings lists where labels are on the left and controls on the right.

Label-left alignment creates a clean two-column layout with settings on the left and controls on the right.

Don't

Mix label positions within the same settings list or preference panel.

Mixing left and right label positions breaks the visual alignment of the control column.

Do

Provide an aria-label when using Toggle without a label prop next to a self-describing heading.

An aria-label communicates the toggle purpose to screen readers when the visible heading is outside the control.

Don't

Render a Toggle with no label prop and no aria-label.

Without a label or aria-label, screen readers announce only 'switch' with no name or purpose.

Props

PropTypeDefaultDescription
labelstringVisible label text. When omitted, supply aria-label for screen readers.
labelPosition'left' | 'right''right'Which side of the track the label appears on. Use left for settings lists with right-aligned controls.
descriptionstringSecondary text rendered below the label. Provides context without extending the label.
checkedbooleanControlled on/off state. When provided, the component is fully controlled and requires an onChange handler.
defaultCheckedbooleanfalseUncontrolled initial state. The toggle starts in the ON position when true.
onChange(newValue: boolean) => voidCalled when the toggle is activated. Receives the new boolean state (true for ON, false for OFF).
disabledbooleanfalseDisables interaction. Applies reduced opacity and not-allowed cursor. Removes from tab order.
size'sm' | 'md''md'Controls the track and thumb dimensions. sm: 36×20 px track, 16 px thumb. md: 44×24 px track, 20 px thumb.
idstringHTML id for the button element. Auto-generated when omitted. Used to associate the label via htmlFor.
classNamestringAdditional Tailwind classes merged onto the root wrapper element.

Accessibility

Role and state

The Toggle renders as a <button> with role="switch". The aria-checked attribute is set to "true" when ON and "false" when OFF. Screen readers announce this as "switch, on" or "switch, off" alongside the accessible name.

<!-- OFF state -->
<button role="switch" aria-checked="false" aria-label="Price drop alerts">...</button>

<!-- ON state -->
<button role="switch" aria-checked="true" aria-label="Price drop alerts">...</button>

Keyboard interaction

KeyAction
TabMove focus to the toggle
Shift + TabMove focus to the previous interactive element
Space or EnterToggle the switch on or off

Label requirement

Every Toggle must have an accessible name — either via the label prop or aria-label. Without a name, screen readers announce only "switch" with no indication of what is being toggled.

{/* Correct — label prop */}
<Toggle label="Price drop alerts" />

{/* Correct — aria-label when label is provided by surrounding context */}
<Toggle aria-label="Enable price drop alerts" />

{/* Incorrect — no accessible name */}
<Toggle />

State communication

The toggle communicates its state through three simultaneous signals — aria-checked, track colour, and thumb position. This means the state is never communicated by colour alone, satisfying WCAG 1.4.1 (Use of Colour).

Immediate-effect pattern

Because toggles take effect instantly, provide clear feedback that the action occurred. For settings that trigger an API call, consider a brief loading state and an aria-live status region that announces success or failure.

{/* Pattern for async toggles — use state in your component */}
{/* 
  const [enabled, setEnabled] = useState(false);
  const [status, setStatus] = useState('');

  async function handleChange(newValue) {
    setEnabled(newValue);
    setStatus('Saving...');
    await saveSetting(newValue);
    setStatus(newValue ? 'Alerts enabled' : 'Alerts disabled');
  }
*/}

Colour and contrast

ElementForegroundBackgroundRatioWCAG
Track ONTeal #005B55
ThumbWhite #FFFFFFTeal #005B557.0:1AAA
Track OFF#CCCCCC
Thumb OFFWhite #FFFFFF#CCCCCC1.6:1Non-text (track is decorative; state is conveyed by aria-checked and position)
Label text#1A1A1AWhite #FFFFFF17.4:1AAA
Description text#666666White #FFFFFF5.9:1AA
Focus ringTeal #005B55White #FFFFFF7.0:1AAA
  • Checkbox — use instead of Toggle when the change requires a Save/Submit action, or for selecting multiple options from a set.
  • Radio Button — use when a single option must be chosen from a small set of mutually exclusive options, rather than a simple on/off state.