Skip to content

Repository files navigation

React Native Segmented Control 2

React Native Segmented Control 2

npm version npm Platform - Android and iOS License: MIT styled with prettier

React Native Segmented Control 2 demo

An iOS-style segmented control for React Native β€” pure JavaScript, one component, zero native code.

Highlights

  • πŸͺΆ Zero dependencies β€” plain React Native, works everywhere RN works
  • 🎬 Springy sliding indicator β€” animated with the native driver
  • πŸŽ› Controlled or uncontrolled β€” pass value to own the state, or let it manage itself
  • 🧩 Any element as a tab β€” strings, icons, or fully composed views
  • 🎨 Fully themeable β€” track, indicator, text, per-tab styles
  • πŸ•³ Inset shadow β€” recess the track with an inner shadow (new in 2.2)
  • 🌍 RTL support β€” indicator direction follows I18nManager
  • ♿️ Accessible β€” tabs expose button role with selected / disabled states
  • πŸ— New Architecture ready β€” even tab distribution and correct indicator offsets under Fabric
  • 🟦 TypeScript β€” written in TS, types shipped in the box

Installation

npm install react-native-segmented-control-2
# or
yarn add react-native-segmented-control-2

Quick start

import SegmentedControl from "react-native-segmented-control-2";

<SegmentedControl
  tabs={["Day", "Week", "Month"]}
  onChange={(index) => console.log(index)}
/>;

That's the whole API: an array of tabs in, the pressed index out.

Usage

Controlled

Pass value to drive the selection from your own state β€” useful when other UI (buttons, gestures, remote state) can change the selection too.

const [index, setIndex] = useState(0);

<SegmentedControl
  tabs={["One", "Two", "Three"]}
  value={index}
  onChange={setIndex}
/>;

Without value, the component manages its own state; use initialIndex to pick the starting tab.

Theming

Track, indicator, and text colors are all styleable:

<SegmentedControl
  tabs={["Income", "Expenses", "Exchange"]}
  style={{ backgroundColor: "#D6E9FF" }}
  activeTabColor="#0A84FF"
  activeTextColor="#FFFFFF"
  textStyle={{ color: "#0A84FF" }}
  onChange={(index) => console.log(index)}
/>

Inset shadow β€” new in 2.2 πŸ•³

Recess the track with an inner shadow so the raised indicator visually sits inside it. Pass true for a sensible default, or a CSS-like boxShadow string for full control:

<SegmentedControl
  tabs={["Inbox", "Archive", "Trash"]}
  insetShadow
  onChange={(index) => console.log(index)}
/>

<SegmentedControl
  tabs={["Inbox", "Archive", "Trash"]}
  insetShadow="inset 0 3px 8px rgba(0, 0, 0, 0.35)"
  onChange={(index) => console.log(index)}
/>

Uses React Native's boxShadow style, available on RN 0.76+. On older versions the prop is ignored and the track stays flat.

Any element as a tab

Tabs don't have to be strings β€” pass any React element:

<SegmentedControl
  tabs={[
    <Icon name="book" />,
    <Icon name="glasses" />,
    <Icon name="history" />,
  ]}
  onChange={(index) => console.log(index)}
/>

Compose freely β€” icon + label rows, badges, whatever you need. See the example app for an icon-and-label "model picker" tab.

Per-tab styles

tabStyle accepts a plain style or a function of the tab index:

<SegmentedControl
  tabs={["S", "M", "L", "XL"]}
  tabStyle={(index) => ({ opacity: index === 3 ? 0.5 : 1 })}
  onChange={(index) => console.log(index)}
/>

Disabled & testing

<SegmentedControl
  tabs={["Draft", "Published"]}
  disabled={isLocked}
  testID="status-switch" // tabs get "status-switch-tab-0", "-tab-1", …
  onChange={setStatus}
/>

Props

Fundamentals

Property Type Default Description
tabs (string | ReactElement)[] required the tabs to render
onChange (index: number) => void required called with the index of the pressed tab
value number undefined selected index, when used as a controlled component

Customization

Property Type Default Description
initialIndex number 0 starting tab for uncontrolled usage
style ViewStyle default style of the outer track container
activeTabColor string #FFF color of the sliding indicator
activeTextColor string #000 text color of the active tab
textStyle TextStyle default style of every tab's text
activeTextStyle TextStyle default extra text style applied only to the active tab
tabStyle ViewStyle | (index: number) => ViewStyle default style of each tab; pass a function for per-tab styles
selectedTabStyle ViewStyle default extra style for the sliding indicator
gap number 2 inset between the indicator and the track edges
insetShadow boolean | string false recess the track with an inner shadow; true uses a default, or pass a boxShadow string to customize (RN 0.76+)
disabled boolean false stop the tabs from responding to presses
testID string undefined set on the container; each tab also gets `${testID}-tab-${index}`

TypeScript

Props and tab types are exported:

import SegmentedControl, {
  SegmentedControlProps,
  TabItem,
} from "react-native-segmented-control-2";

Example app

The example/ folder is an Expo app showcasing every feature β€” it's the app in the demo above, including an in-app light/dark theme switch.

cd example
npm install
npx expo start --ios   # or --android

Credits

Originally inspired by react-native-segmented-control/segmented-control and Karthik-B-06/react-native-segmented-control β€” built as an actively maintained, pure-JavaScript alternative with more customization.

Thanks to @madox2 for controlled-component support and @philo23 for removing the screen-width dependency.

Author

FreakyCoder, kurayogun@gmail.com

License

React Native Segmented Control 2 is available under the MIT license. See the LICENSE file for more info.

Releases

Used by

Contributors

Languages