Segmented control
Free · MITReact Native · ExpoTwo 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.
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.jsonThen 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
| Prop | Type | Default |
|---|---|---|
| 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
| Prop | Type | Default |
|---|---|---|
| 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.