Segmented control

Free · MITReact Native · Expo

Two to five views of the same thing with an elastic highlight that stretches toward the new segment, can be dragged, and ticks at every boundary. Includes a billing toggle.

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/segmented-control.json

Then run npx expo install --fix so react-native-gesture-handler, react-native-reanimated match your Expo SDK. Files land in components/goodui-native/segmented-control.

  • Wrap the app in GestureHandlerRootView and ThemeProvider.

Usage

import { SegmentedControl, BillingToggle } from "@/components/goodui-native/segmented-control/segmented-control";

<SegmentedControl
  accessibilityLabel="Range"
  value={range}
  onValueChange={setRange}
  options={[
    { value: "day", label: "Day" },
    { value: "week", label: "Week" },
    { value: "month", label: "Month" },
  ]}
/>

<BillingToggle value={period} onValueChange={setPeriod} savings="Save 33%" />

When to use

Use it for
  • Switching between two to five views of the same data: day, week, month.
  • A monthly and yearly price switch on a paywall, with a savings badge.
Reach for something else
  • Navigation between screens: use tabs.
  • More than five options or long labels: use a picker or a list.
  • An on or off setting: use a switch.

API reference

SegmentedControl

PropTypeDefault
options*
Two to five options. Each has a value, a label and an optional badge.
SegmentedOption<T>[]—
value*
The selected option value.
T—
onValueChange*
Called with the new value on tap, or on release after a drag.
(value: T) => void—
size
`md` is 40 pt tall, `sm` 32 pt.
"sm" | "md""md"
style
Style for the outer container.
StyleProp<ViewStyle>—
accessibilityLabel
Overrides the label read by screen readers.
string—
testID
Identifier for end-to-end tests.
string—

BillingToggle

PropTypeDefault
value*
The selected period.
"monthly" | "yearly"—
onValueChange*
Called with the new period.
(value: BillingPeriod) => void—
savings
Badge on the yearly option, for example "Save 33%".
string—
labels
Overrides for the built-in strings; a GooduiLocaleProvider pack sets them app-wide.
Partial<BillingToggleLabels>—
style
Style for the outer container.
StyleProp<ViewStyle>—

Motion and haptics

  • The highlight's leading edge travels on the snappy spring and its trailing edge on the smooth one, so it stretches and settles.
  • Dragging moves the highlight with the finger; release snaps to the nearest segment with velocity.
  • Label colors follow how much of the highlight covers them.
  • Reduced motion: the highlight moves without stretching.
  • Haptic: select on a tap that changes the value
  • Haptic: select at each segment boundary while dragging
  • Highlight position and label colors run on the UI thread.

Accessibility

  • The track is a tablist and each segment a tab with its selected state. Badges are part of the label.

Notes for AI

  • It is controlled: pass value and onValueChange. Values are strings.
  • Pair BillingToggle with rolling-number for the price.
  • Give it an accessibilityLabel that names what it switches.