Modern horizontal scrolling menu for React Native.
Plain React source + TypeScript definitions. Auto-centers the active chip, supports custom items, indicators, and optional tab scenes with swipe + fade/scale effects.
| Tab scenes (swipe + content) | Chips / autoCenter / indicator |
|---|---|
![]() |
![]() |
Recorded from the iOS Simulator playground (example/).
npm install @sekizlipenguen/react-native-scroll-menu
# or
yarn add @sekizlipenguen/react-native-scroll-menuPeer deps: react, react-native (>= 0.64).
cd example
npm install
bundle install && bundle exec pod install --project-directory=ios
npm start -- --port 8083
npx react-native run-ios --port 8083example/ is in git for development — not published to npm.
import React, {useState} from 'react';
import {Text, View} from 'react-native';
import ScrollingButtonMenu from '@sekizlipenguen/react-native-scroll-menu';
export default function Example() {
const [selected, setSelected] = useState('1');
return (
<View>
<ScrollingButtonMenu
items={[
{id: '1', name: 'Life'},
{id: '2', name: 'Time'},
{id: '3', name: 'Faith'},
{id: '4', name: 'Cosmos'},
{id: '5', name: 'Fall'},
]}
selected={selected}
onPress={(item) => setSelected(item.id)}
/>
<Text>Selected: {selected}</Text>
</View>
);
}Instagram / App Store style: chips on top, a page underneath.
import {ScrollingTabScenes} from '@sekizlipenguen/react-native-scroll-menu';
<ScrollingTabScenes
items={[
{id: 'overview', name: 'Overview'},
{id: 'gallery', name: 'Gallery'},
{id: 'reviews', name: 'Reviews'},
]}
selected={selected}
onChange={(item) => setSelected(item.id)}
animation="slide" // 'slide' | 'fade' | 'scale'
swipeEnabled
indicator
renderScene={({item}) => <YourPage id={item.id} />}
/>- Height is automatic (measures active scene). Pass
heightonly for a fixed viewport. slide= pager + parallax fade/scalefade/scale= cross-fade transitions- Menu props (
gap,indicator, styles, …) are forwarded toScrollingButtonMenu.
Heavier alternatives if you need Material TabView / collapsing headers:
react-native-tab-view · reanimated-tab-view · collapsible-fluid-tabs
items={[
{id: '1', name: 'Life'},
{
id: '2',
component: (
<View style={{alignItems: 'center'}}>
<Image source={require('./icon.png')} style={{width: 24, height: 24}} />
<Text style={{fontSize: 12, marginTop: 4}}>Icon</Text>
</View>
),
},
]}If component is set, name is not rendered.
items={[
{id: '1', name: 'Home'},
{
id: '2',
name: 'Profile',
onPress: () => console.log('Profile pressed!'),
},
]}Item-level onPress overrides the menu-level handler.
<ScrollingButtonMenu
items={[
{id: '1', name: 'Home'},
{id: '2', name: 'Soon', disabled: true},
{id: '3', name: 'Settings'},
]}
selected={selected}
onPress={(item) => setSelected(item.id)}
gap={12}
indicator
indicatorStyle={{backgroundColor: '#007AFF', height: 3}}
// unstyled
/>const menuRef = useRef(null);
<ScrollingButtonMenu
ref={menuRef}
items={items}
selected={selected}
onPress={(item) => setSelected(item.id)}
/>
menuRef.current?.scrollToId('3');
menuRef.current?.scrollToIndex(0);<ScrollingButtonMenu
items={items}
selected={selected}
onPress={(item) => setSelected(item.id)}
renderItem={({item, selected}) => (
<Text style={{fontWeight: selected ? '700' : '400'}}>{item.name}</Text>
)}
/>| Key | Type | Default | Description |
|---|---|---|---|
items |
MenuItem[] |
— | Menu items (required) |
onPress |
(item) => void |
— | Item press handler |
selected |
string | number |
— | Selected id (controlled) |
upperCase |
boolean |
false |
Uppercase labels |
selectedOpacity |
number |
0.7 |
Press opacity |
gap |
number |
10 |
Space between items |
autoCenter |
boolean |
true |
Center selected item |
unstyled |
boolean |
false |
Skip built-in chip styles |
indicator |
boolean |
false |
Sliding underline |
indicatorStyle |
ViewStyle |
— | Indicator style |
renderItem |
(info) => ReactNode |
— | Custom item renderer |
containerStyle |
ViewStyle |
— | Outer container |
contentContainerStyle |
ViewStyle |
— | Scroll content |
scrollStyle |
ViewStyle |
— | ScrollView style |
textStyle / activeTextStyle |
TextStyle |
— | Label styles |
buttonStyle / activeButtonStyle |
ViewStyle |
— | Item styles |
firstButtonStyle / lastButtonStyle |
ViewStyle |
— | Edge item styles |
activeColor |
string |
— | Active text color |
activeBackgroundColor |
string |
#1e1e1e |
Active background |
scrollEnabled |
boolean |
true |
Horizontal scroll |
showsHorizontalScrollIndicator |
boolean |
false |
Scroll indicator |
keyboardShouldPersistTaps |
string | boolean |
'always' |
Keyboard taps |
bounces |
boolean |
true |
iOS bounce |
testID / accessibilityLabel |
string |
— | Testing / a11y |
| Key | Type | Description |
|---|---|---|
id |
string | number |
Unique id (required) |
name |
string |
Label |
component |
ReactNode |
Custom content |
onPress |
(item) => void |
Per-item handler |
disabled |
boolean |
Disable item |
accessibilityLabel / testID |
string |
A11y / tests |
| Method | Description |
|---|---|
scrollToId(id, animated?) |
Center by id |
scrollToIndex(index, animated?) |
Center by index |
| Key | Type | Default | Description |
|---|---|---|---|
onChange |
(item, index) => void |
— | Selection changed |
renderScene |
(info) => ReactNode |
— | Scene renderer |
scenes |
Record<id, ReactNode> |
— | Alternate scene map |
height |
number |
auto | Fixed height (optional) |
swipeEnabled |
boolean |
true |
Swipe between scenes |
animation |
'slide' | 'fade' | 'scale' |
'slide' |
Transition style |
sceneContainerStyle |
ViewStyle |
— | Scene wrapper style |
Ref: goToId(id), goToIndex(index).
Issues & feedback: GitHub Issues.

