UnflowUI
Components

Switch

A binary toggle input rendered as a native checkbox styled to look like a sliding switch.

Overview

Switch wraps a native <input type="checkbox"> with a custom CSS design. Because it is a real checkbox, it participates in forms, supports keyboard navigation natively, and works with standard React state patterns.

Two implementation notes worth knowing:

  • className applies directly to the <input> element, not a wrapper div — be precise with your overrides.
  • readonly uses pointer-events-none + aria-readonly rather than the HTML readonly attribute (which doesn't exist on <input type="checkbox">).

Import

import { Switch } from '@unflow/ui/components/Switch';

Props

PropTypeDefaultDescription
checkedbooleanControlled checked state
onChangeChangeEventHandler<HTMLInputElement>Change handler
disabledbooleanfalseDisables interaction, sets aria-disabled and HTML disabled
readonlybooleanfalsePrevents interaction via pointer-events-none
defaultCheckedbooleanUncontrolled initial value

All standard HTMLInputElement props are forwarded. Always provide aria-label.

States

off
on
disabled off
disabled on
readonly
<Switch checked={false} onChange={setChecked} aria-label="Toggle feature" />
<Switch checked={true} onChange={setChecked} aria-label="Toggle feature" />
<Switch checked={false} disabled aria-label="Disabled off" />
<Switch checked={true} disabled aria-label="Disabled on" />
<Switch checked={true} readonly aria-label="Read-only on" />

Controlled usage

import { useState } from 'react';
import { Switch } from '@unflow/ui/components/Switch';

function FeatureToggle() {
  const [enabled, setEnabled] = useState(false);

  return (
    <Switch
      checked={enabled}
      onChange={(e) => setEnabled(e.target.checked)}
      aria-label="Enable dark mode"
    />
  );
}

Uncontrolled usage

<Switch defaultChecked aria-label="Enable notifications" />

Readonly vs Disabled

Both prevent the user from changing the value, but communicate different semantics:

  • disabled — the control is not available. Uses HTML disabled + aria-disabled="true". The value is excluded from form submission.
  • readonly — the value is fixed but the control is present and accessible. Uses pointer-events-none + aria-readonly="true". The value is included in form submission.

Accessibility

  • Renders <input type="checkbox" role="switch"> — natively keyboard-operable (Space to toggle).
  • aria-checked mirrors the checked prop.
  • A console warning fires in development if aria-label is missing.
  • Associate a visible label using <label htmlFor={id}> alongside the Switch.
<label htmlFor="dark-mode" className="flex items-center gap-2 cursor-pointer">
  <Switch id="dark-mode" checked={dark} onChange={toggle} aria-label="Dark mode" />
  <span>Dark mode</span>
</label>