react-native-magic-tab-bar
v2.1.0
Published
Customizable floating tab bar for Expo Router and React Navigation (bare React Native). Bring your own icons and labels.
Maintainers
Readme
react-native-magic-tab-bar is a drop-in custom bottom tab bar for React Native. Instead of the default tab bar, you get a floating, animated bar with an active-tab pill, badges, labels, haptics, glass/blur backgrounds and full theming — on both iOS and Android. The same component works whether your app uses Expo Router or React Navigation (@react-navigation/bottom-tabs), so you can search less and ship faster.
Two entry points, one look:
- Expo Router →
MagicTabsfromreact-native-magic-tab-bar(see Quick start).- Bare React Native / React Navigation →
MagicTabBarNavigationfromreact-native-magic-tab-bar/react-navigation(see React Navigation).Pick whichever entry point matches your app — you never pull in the other framework's dependencies.
Features
- 🎯 Drop-in for both — one component for Expo Router and React Navigation; bring your own icons.
- ✨ Animated active pill with a spring you can tune.
- 🏷️ Flexible labels — beside or below the icon; show on the active tab, always, or never.
- 🔴 Badges — dots or counts on any tab.
- 🧊 Glass / blur / transparent backgrounds (native iOS Liquid Glass supported).
- 📳 Haptics and press callbacks (great for "scroll to top" on re-press).
- ➕ Action (FAB) tab for a raised center button.
- 🪶 Light mode — a compact, icon-only bar you can switch on per tab (e.g. only on an immersive full-screen feed), with a smooth transition.
- 🎨 Fully themeable via a single
themeprop.
Table of contents
- Compatibility
- Installation
- Quick start (Expo Router)
- React Navigation (bare React Native)
- Recipes
- API
- Development
- Roadmap
- License
Screenshots
Compatibility
| Environment | How you use it | Supported |
| --- | --- | :---: |
| Expo Router (SDK 56+) | MagicTabs from react-native-magic-tab-bar | ✅ |
| React Navigation (@react-navigation/bottom-tabs v6 & v7) | MagicTabBarNavigation from react-native-magic-tab-bar/react-navigation | ✅ |
| Bare React Native CLI | via React Navigation | ✅ |
| Expo (managed & prebuild) | via Expo Router | ✅ |
| iOS · Android | — | ✅ |
| New Architecture (Fabric) | — | ✅ |
| TypeScript | types bundled, no @types needed | ✅ |
Installation
npm install react-native-magic-tab-bar
# yarn add react-native-magic-tab-bar
# pnpm add react-native-magic-tab-bar
# bun add react-native-magic-tab-barThen install the peer dependencies for your framework 👇
Expo Router
Requirements: an Expo Router project (Expo SDK 56+) with an
app/directory.
Peer dependencies (normally already present in an Expo Router app):
npx expo install expo-router react-native-reanimated react-native-safe-area-context react-native-workletsMake sure the Reanimated/Worklets Babel plugin is enabled (Expo SDK 56's babel-preset-expo configures this automatically).
Optional — install only if you use the matching feature:
npx expo install expo-glass-effect # native iOS Liquid Glass (glass prop)
npx expo install expo-haptics # selection haptics (haptics prop)
npx expo install @expo/vector-icons # only for the /default-tabs demo setReact Native CLI (React Navigation)
Requirements: a bare React Native CLI app using
@react-navigation/bottom-tabs.
npm install @react-navigation/native @react-navigation/bottom-tabs \
react-native-screens react-native-safe-area-context \
react-native-reanimated react-native-workletsAdd the Worklets/Reanimated plugin last in your babel.config.js:
module.exports = {
presets: ['module:@react-native/babel-preset'],
plugins: ['react-native-worklets/plugin'],
};iOS: cd ios && pod install. No Expo packages are required — the glass and haptics props are Expo-only and simply no-op here unless you install expo-glass-effect / expo-haptics.
See the runnable example-cli app for a complete setup.
Quick start
Use MagicTabs as your tab navigator in app/_layout.tsx. Each entry in tabs maps a route to an icon and label:
// app/_layout.tsx
import { MagicTabs } from "react-native-magic-tab-bar";
import { Ionicons } from "@expo/vector-icons";
export default function Layout() {
return (
<MagicTabs
tabs={[
{ name: "index", href: "/", label: "Home", icon: ({ color, size }) => <Ionicons name="home" color={color} size={size} /> },
{ name: "search", href: "/search", label: "Search", icon: ({ color, size }) => <Ionicons name="search" color={color} size={size} /> },
{ name: "profile", href: "/profile", label: "Profile", icon: ({ color, size }) => <Ionicons name="person" color={color} size={size} /> },
]}
/>
);
}namemust match the route file in yourapp/directory (e.g.index,search,profile).hrefis where the tab navigates.iconreceives{ focused, color, size }so you can swap glyphs/colors for the active state.
MagicTabsrenders Expo Router's<Tabs>for you — put it straight inapp/_layout.tsx. There's no separate<Tabs>wrapper to set up.
Tip — filled vs. outline icons: use focused to swap the glyph on the active tab:
icon: ({ focused, color, size }) => (
<Ionicons name={focused ? "home" : "home-outline"} color={color} size={size} />
);Want a ready-made set for a quick look? Import the demo tabs from the subpath (this is the only thing that pulls in
@expo/vector-icons, so the core stays dependency-free):import { defaultTabs } from "react-native-magic-tab-bar/default-tabs"; <MagicTabs tabs={defaultTabs} />;
React Navigation (bare React Native)
In a bare React Native app you use MagicTabBarNavigation as the tabBar of a
bottom tab navigator.
It takes the same tab config as MagicTabs — just without href (React
Navigation routes by name) — so the bar looks and behaves identically.
import { NavigationContainer } from "@react-navigation/native";
import { createBottomTabNavigator } from "@react-navigation/bottom-tabs";
import { SafeAreaProvider } from "react-native-safe-area-context";
import { MagicTabBarNavigation } from "react-native-magic-tab-bar/react-navigation";
import Ionicons from "react-native-vector-icons/Ionicons";
const Tab = createBottomTabNavigator();
const tabs = [
{ name: "Home", label: "Home", icon: ({ color, size }) => <Ionicons name="home" color={color} size={size} /> },
{ name: "Search", label: "Search", icon: ({ color, size }) => <Ionicons name="search" color={color} size={size} /> },
{ name: "Profile", label: "Profile", icon: ({ color, size }) => <Ionicons name="person" color={color} size={size} /> },
];
export default function App() {
return (
<SafeAreaProvider>
<NavigationContainer>
<Tab.Navigator
screenOptions={{ headerShown: false }}
tabBar={(props) => <MagicTabBarNavigation {...props} tabs={tabs} />}
>
<Tab.Screen name="Home" component={HomeScreen} />
<Tab.Screen name="Search" component={SearchScreen} />
<Tab.Screen name="Profile" component={ProfileScreen} />
</Tab.Navigator>
</NavigationContainer>
</SafeAreaProvider>
);
}- Each
tabs[].namemust match a<Tab.Screen name>. - No
href— that's an Expo Router concept; React Navigation navigates byname. - Every visual prop from
MagicTabs(theme,showLabels,labelPosition,variant,isTransparent,renderBackground,haptics,onTabPress, …) works here too, plus all the Recipes below.
Icons are bring-your-own, exactly like the Expo entry — the
iconrender function is identical. This example usesreact-native-vector-icons(the bare-RN counterpart of@expo/vector-icons). Bundle its fonts the CocoaPods way — notreact-native-asset: on iOS the pod bundles the font files (just add aUIAppFontsentry toInfo.plist), and on Android apply the package'sfonts.gradleinapp/build.gradle. The only Expo-specific props areglass(expo-glass-effect) andhaptics(expo-haptics), which no-op in bare RN unless you add those modules.
Prefer to keep the config next to your screens instead of a
tabsarray? The config is just data — build it however you like; onlynameandiconare required per tab.
Recipes
The recipes below are written with
MagicTabs(Expo Router), but every prop shown also works onMagicTabBarNavigation— pass it the same way.
Labels
Labels are beside the icon by default (labelPosition="right"), shown only on the active tab.
<MagicTabs tabs={tabs} labelPosition="bottom" showLabels="always" /> // Material-style bar
<MagicTabs tabs={tabs} showLabels={false} /> // icon-only bar| showLabels | Effect |
| --- | --- |
| true / "active" | Label on the focused tab only (default) |
| "always" | Label on every tab (requires labelPosition="bottom") |
| false / "never" | Icon-only |
labelPosition is "right" (default) or "bottom". You can also override visibility per tab with showLabel on the tab config.
Badges
{ name: "inbox", href: "/inbox", label: "Inbox", badge: 5, icon }, // count bubble
{ name: "alerts", href: "/alerts", label: "Alerts", badge: true, icon }, // dotbadge accepts a number, string, or boolean (true = dot). Numbers above 99 show as 99+. Colors come from theme.badgeColor / theme.badgeTextColor.
Haptics and press callbacks
<MagicTabs
tabs={tabs}
haptics // selection haptic on tap (needs expo-haptics)
onTabPress={(name, focused) => {
if (focused) scrollToTop(name); // re-pressing the active tab
}}
/>onTabPress / onTabLongPress receive (name, focused), where focused tells you the tab was already active — perfect for scroll-to-top or reset-stack behavior.
Disabled tabs
{ name: "soon", href: "/soon", label: "Soon", disabled: true, icon }Dims the tab and blocks navigation to it.
Action (FAB) tab
Render a raised, circular center button:
{ name: "create", href: "/create", variant: "action", icon }Uses theme.actionColor / theme.actionIconColor.
Glass, blur and transparency
<MagicTabs tabs={tabs} glass /> // native iOS Liquid Glass (iOS 26+)
<MagicTabs tabs={tabs} isTransparent transparency={0.4} />// translucent bar
<MagicTabs tabs={tabs} renderBackground={() => <MyBlurView />} /> // any custom backgroundglassusesexpo-glass-effecton iOS 26+ and falls back to the translucentbarColorelsewhere.renderBackgroundrenders any view behind the bar; when omitted, a solidbarColoris used.
Light mode (compact bar)
A shorter, icon-only bar (65% width, floating higher). Turn it on for the whole bar, or per tab so it only appears on an immersive screen:
// Whole bar
<MagicTabs tabs={tabs} isLight lightBottomMargin={40} />
// Per tab — the bar morphs to light only while "explore" is the active route
<MagicTabs
tabs={[
{ name: "index", href: "/", label: "Home", icon },
{ name: "explore", href: "/explore", label: "Explore", icon, isLight: true },
{ name: "profile", href: "/profile", label: "Profile", icon },
]}
/>The transition between the normal and light bar is animated. lightBottomMargin (default 14) controls the extra gap above the screen edge in light mode.
Theming
Everything visual — colors, sizes, corner radius, the animation spring — lives in one theme prop. Override any subset; the rest falls back to defaultTheme.
The colors that matter most:
| Token | Controls |
| --- | --- |
| barColor | the bar's background |
| activePillColor | the pill highlight behind the active tab |
| activeColor | the active tab's icon + label |
| inactiveColor | the icons of inactive tabs |
| badgeColor / badgeTextColor | the badge bubble + its text |
| actionColor / actionIconColor | the action (FAB) tab |
<MagicTabs
tabs={tabs}
theme={{
barColor: "#17171C", // dark bar
activePillColor: "#2563EB", // blue highlight behind the active tab
activeColor: "#FFFFFF", // active icon/label
inactiveColor: "#9BA0AA", // inactive icons
radius: 24, // rounder bar & pill
spring: { mass: 0.6, damping: 16, stiffness: 200 }, // snappier animation
}}
/>See the full token list with defaults for sizes (iconSize, height, fontSize), spacing (horizontalMargin, bottomInset) and more.
API
<MagicTabs />
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| tabs | MagicTabConfig[] | required | The tabs, in order. |
| theme | Partial<MagicTabBarTheme> | defaultTheme | Override any visual token. |
| showLabels | boolean \| 'active' \| 'always' \| 'never' | 'active' | When labels are shown. 'always' needs labelPosition="bottom". |
| labelPosition | 'right' \| 'bottom' | 'right' | Label beside or below the icon. |
| variant | 'floating' \| 'docked' | 'floating' | Float over content, or dock in flow. |
| isLight | boolean | false | Force the compact light bar for all tabs. |
| lightBottomMargin | number | 14 | Extra bottom gap in light mode only. |
| isTransparent | boolean | false | Make the bar background see-through. |
| transparency | number | 0.6 | Bar opacity 0–1 while isTransparent. |
| glass | boolean | false | Native iOS Liquid Glass (needs expo-glass-effect). |
| renderBackground | () => ReactNode | — | Custom background (blur/glass) behind the bar. |
| haptics | boolean | false | Selection haptic on press (needs expo-haptics). |
| onTabPress | (name, focused) => void | — | Fired on tab press. |
| onTabLongPress | (name, focused) => void | — | Fired on tab long-press. |
MagicTabConfig
| Field | Type | Description |
| --- | --- | --- |
| name | string | Route name. Expo Router: matches the file in app/. React Navigation: matches <Tab.Screen name>. |
| href | MagicHref? | Destination, e.g. / or /search. Required for Expo Router, ignored by React Navigation. |
| icon | (p: MagicTabIconProps) => ReactNode | Renders the icon. Gets { focused, color, size }. |
| label | string? | Text label. |
| showLabel | boolean? | Per-tab override of showLabels. |
| badge | number \| string \| boolean? | Dot (true) or count bubble. |
| disabled | boolean? | Dim the tab and block navigation. |
| variant | 'action'? | Render as a raised FAB button. |
| isLight | boolean? | Switch the whole bar to light mode while this tab is active. |
<MagicTabBarNavigation />
The React Navigation binding, imported from react-native-magic-tab-bar/react-navigation. Pass it to Tab.Navigator's tabBar prop.
It receives React Navigation's BottomTabBarProps (spread from {...props}) plus:
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| tabs | MagicNavigationTab[] | required | Per-tab config keyed by route name (same as MagicTabConfig but without href). |
All other visual props are identical to <MagicTabs />: theme, showLabels, labelPosition, variant, isLight, lightBottomMargin, isTransparent, transparency, glass, renderBackground, haptics, onTabPress, onTabLongPress.
MagicNavigationTabisOmit<MagicTabConfig, 'href'>.
MagicTabBarTheme
| Token | Default | |
| --- | --- | --- |
| barColor | rgba(38,38,40,0.94) | Bar background |
| activePillColor | rgba(120,120,124,0.55) | Active-tab pill |
| activeColor | #FFFFFF | Active icon/label color |
| inactiveColor | #FFFFFF | Inactive icon color |
| iconSize | 22 | Icon size |
| fontSize | 12 | Active label size |
| height | 56 | Bar height |
| radius | 28 | Bar & pill corner radius |
| badgeColor | #FF3B30 | Badge background |
| badgeTextColor | #FFFFFF | Badge text |
| actionColor | #0A84FF | Action (FAB) background |
| actionIconColor | #FFFFFF | Action (FAB) icon |
| horizontalMargin | 14 | Side margin from screen edges |
| bottomInset | 10 | Extra space below the bar |
| spring | { mass: 0.6, damping: 18, stiffness: 180 } | Pill/label animation |
Exported types
MagicTabConfig, MagicHref, MagicTabIconProps, MagicTabBarTheme, MagicTabBarVariant, MagicTabPressHandler, MagicLabelMode, MagicLabelPosition, MagicSpringConfig, plus the defaultTheme value.
From react-native-magic-tab-bar/react-navigation: MagicTabBarNavigation, MagicTabBarNavigationProps, MagicNavigationTab.
defaultTabsis exported from thereact-native-magic-tab-bar/default-tabssubpath (not the main entry), so the core adds zero runtime dependencies.
Development
This repo is a monorepo: the library lives at the root (src/), with two runnable example apps that import the library straight from source:
example— Expo Router app (MagicTabs).example-cli— bare React Native CLI app (MagicTabBarNavigation).
# Expo Router example
npm install # from the repo root: installs example deps + build tooling
cd example && npx expo start
# React Native CLI example
cd example-cli && npm install
npm run ios # or: npm run androidEditing files in src/ hot-reloads in whichever example is running.
Building / publishing
Publishing is restricted to package maintainers.
npm run build # react-native-builder-bob -> lib/ (ESM + d.ts)
npm publishRoadmap
- Sliding active-pill that animates between tabs (currently per-tab pill).
- ✅ ~~A React Navigation (
@react-navigation/bottom-tabs) adapter for bare RN.~~ Shipped — see React Navigation.
License
MIT
