Switch

Free · MITReact Native · Expo

An iOS-style switch with squash and stretch: the thumb stretches toward where it is going on touch down, then travels on a spring while the track color follows. Settings-row layout, tones, sizes and optimistic async changes.

View as Markdown
Try it on your phone
Scan to open this demo in your phone's browser and use it with your fingers.

Installation

$ npx shadcn@latest add https://goodui.dev/native/r/switch.json

Then run npx expo install --fix so react-native-reanimated match your Expo SDK. Files land in components/goodui-native/switch.

Usage

import { useState } from "react";
import { Switch } from "@/components/goodui-native/switch/switch";

export function PushSetting() {
  const [push, setPush] = useState(true);
  return (
    <Switch
      label="Push notifications"
      description="Bookings, reminders and messages"
      value={push}
      onValueChange={async (next) => {
        await api.saveSettings({ push: next });
        setPush(next);
      }}
    />
  );
}

When to use

Use it for
  • On or off settings that take effect immediately: notifications, dark mode, Face ID.
  • Settings saved to a server: return the promise from onValueChange.
  • A settings list: pass label and description for the full row.
Reach for something else
  • Choices submitted later with a form, or several options in a list: use checkbox.
  • One of two named views: use segmented-control.
  • A destructive change: use hold-to-confirm.

Examples

Inline with a screen reader label

<Switch accessibilityLabel="Round-up savings" defaultValue tone="success" />

Small

<Switch size="sm" accessibilityLabel="Show weekends" value={weekends} onValueChange={setWeekends} />

API reference

PropTypeDefault
value
On or off (controlled). Pair it with `onValueChange`.
boolean—
defaultValue
Initial state when uncontrolled.
booleanfalse
onValueChange
Called with the new state. Return a promise (for example a settings request) and the switch flips right away, shows a spinner in the thumb while pending, and flips back with an error haptic and a shake if it rejects.
(value: boolean) => unknown—
tone
Track color when on. `accent` by default; `success`, `warning` and `danger` only when the setting means that status.
"success" | "warning" | "danger" | "accent""accent"
size
40 × 24 (`sm`) or 51 × 31 pt (`md`, the iOS size). The touch target stays 44 pt.
"sm" | "md""md"
label
Turns it into a settings row: the label on the leading side, the switch trailing, the whole row toggles.
string—
description
Secondary line under the row label.
string—
disabled
Dims the control and ignores presses.
booleanfalse
haptic
`false` silences the select haptic on change.
booleantrue
accessibilityLabel
Screen reader label when there is no visible `label`.
string—
accessibilityHint
Describes what happens on activation, read after the label.
string—
labels
Overrides for the built-in strings; a GooduiLocaleProvider pack sets them app-wide.
Partial<SwitchLabels>—
style
Style for the outer container.
StyleProp<ViewStyle>—
testID
Identifier for end-to-end tests.
string—

Motion and haptics

  • Press: the thumb widens toward the far side and squashes to 92 % height on the snappy spring, from touch down.
  • Change: the thumb travels on the snappy spring and the track color interpolates with it.
  • Pending: a spinner turns inside the thumb. Rejected: the switch flips back and shakes.
  • Reduced motion: no squash, stretch or shake; the thumb moves with a short timing.
  • Haptic: toggle-on or toggle-off on change
  • Haptic: error when a returned promise rejects
  • Thumb geometry and colors animate on the UI thread; a toggle is one React update.

Accessibility

  • accessibilityRole is switch with checked, disabled and busy (while a returned promise is pending) in accessibilityState.
  • In the row layout the whole row is the switch, labelled 'label, description'. Inline, pass accessibilityLabel.
  • A rejected change is announced ('Couldn't change the setting').
  • The inline switch has hitSlop up to a 44 pt target; the row is at least 44 pt tall.

Notes for AI

  • Controlled (value + onValueChange) or uncontrolled (defaultValue).
  • Return a promise from onValueChange to get optimistic flipping, a pending spinner and automatic revert on failure. Do not add your own loading state.
  • Use label and description instead of building your own row around it.
  • Use tone only when the setting means a status; the default accent is right for almost everything.