A smooth and interactive animated top tab bar inspired by Apple’s Mail app, built with React Native, Reanimated, and Expo. Perfect for creating engaging top-navigation experiences in your mobile apps.
- Customizable dynamic TopTabs (
TopTabs
) component - Full TypeScript support
- Easy integration with Expo/React Native projects
- Supports custom icons and titles
- Reactive tab change callback via
onTabChange
- Smooth routing integration with
expo-router
- Flexible rendering of tab-specific components
# npm
npm install rn-toptabs
# yarn
yarn add rn-toptabs
# bun
bun add rn-toptabs
import React, { useState } from "react";
import { View, Text } from "react-native";
import { TopTabs, TabButtonConfig } from "rn-toptabs";
const Chat = () => {
return (
<View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
<Text>Chat Page</Text>
</View>
)
}
const tabs: TabButtonConfig[] = [
{
label: "Chat",
tabKey: "chat",
image: require("../assets/icon/chat-default.png"),
activeTint: "#fff",
defaultTint: "#0B75DF",
component: Chat
},
{
label: "Cart",
tabKey: "cart",
image: require("../assets/icon/cart-white.png"),
activeTint: "#fff",
defaultTint: "#5150C9",
component: () => (
<View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
<Text>Cart Page</Text>
</View>
),
},
];
export default function App() {
// Optional: Get reactive active tab object
const [activeTab, setActiveTab] = useState<TabButtonConfig>(tabs[0]);
return (
<View style={{ flex: 1 }}>
<TopTabs
tabs={tabs}
syncWithRouter={true} // Enable URL sync
separate // Optional separator below tab bar
onTabChange={setActiveTab} // Reactive tab state callback
/>
</View>
);
}
This package requires the following peer dependencies in your project:
expo >=49.0.0 <53.0.0
react >=17.0.0 <19.0.0
react-native >=0.72.0 <0.77.0
expo-router >=4.0.21
react-native-reanimated >=3.16.1
You must install these in your app. Any compatible version above the minimum will work—no need to match the exact version.
Example:
npm install expo-router@^4.0.21 react-native-reanimated@^3.16.1
If your project already uses these dependencies, you do not need to change anything.
Prop | Type | Description |
---|---|---|
tabs |
TabButtonConfig[] |
Array of tab definitions |
initialIndex |
number |
Initial active tab index (default: 0 ) |
syncWithRouter |
boolean |
Sync active tab with URL query (expo-router ) |
separate |
boolean |
Show a separator line below the tab bar |
onTabChange |
(tab: TabButtonConfig) => void |
Callback triggered whenever the active tab changes |
Prop | Type | Description |
---|---|---|
label |
string |
Tab title |
tabKey |
string |
Unique key for the tab |
image |
any |
Tab icon |
activeTint |
string |
Color when tab is active |
defaultTint |
string |
Default tab color |
component |
React.ComponentType<any> |
Component to render for this tab |
TopTabs
can automatically sync the active tab with your URL using expo-router
, enabling:
- Deep linking to specific tabs
- Preserving tab state across navigation
- Bookmarkable tab URLs
-
Sync from URL on mount If
syncWithRouter
is enabled and the URL contains atab
query parameter,TopTabs
will automatically select the corresponding tab.// Example URL: /app?tab=cart // "Cart" tab will be active on load
-
Update URL on tab change When a user selects a tab,
TopTabs
updates the URL query automatically:// Active tab: "Chat" // URL updates to: /app?tab=chat
-
Works with browser history & deep links Users can share links with a specific tab selected or navigate back/forward while keeping the tab state intact.
-
Optional reactive tab state Use the
onTabChange
callback to get the active tab object whenever it changes:<TopTabs tabs={tabs} syncWithRouter onTabChange={(tab) => console.log("Current tab:", tab.label)} />
- Prefer using the
onTabChange
callback instead of global state for active tab data. - Works seamlessly with
expo-router
for consistent URL-based tab state. - Optional separator can be enabled with the
separate
prop. - Supports custom tab icons, tint colors, and dynamic components per tab.