npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

react-native-magic-tab-bar

v3.0.0

Published

Custom animated bottom tab bar for React Native — floating, glass/blur, badges & haptics. One component for Expo Router and React Navigation (bare RN CLI). iOS & Android, TypeScript.

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 RouterMagicTabs from react-native-magic-tab-bar (see Quick start).
  • Bare React Native / React NavigationMagicTabBarNavigation from react-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.
  • 📐 Sized by its tabs — the bar hugs its content and centers, so two tabs give a compact pill, not a full-width bar with a hole in the middle.
  • 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.
  • Accessible by default — every tab is a labelled tab in a tablist, badges are announced ("Alerts, 3"), and touch targets meet the platform minimums.
  • 🎨 Fully themeable via a single theme prop.

Table of contents

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 | ✅ |

Why react-native-magic-tab-bar?

How it compares to other ways of building a React Native bottom tab bar:

| | Default @react-navigation/bottom-tabs | Roll your own tabBar | react-native-magic-tab-bar | | --- | :---: | :---: | :---: | | Floating / docked bar | ❌ | 🛠️ DIY | ✅ built-in | | Animated active pill (Reanimated) | ❌ | 🛠️ DIY | ✅ | | Badges, labels, haptics | partial | 🛠️ DIY | ✅ | | Glass / blur / iOS Liquid Glass | ❌ | 🛠️ DIY | ✅ | | Action / FAB center tab | ❌ | 🛠️ DIY | ✅ | | Works with Expo Router and React Navigation | — | — | ✅ one component | | Bring your own icons (no icon lib forced) | ✅ | ✅ | ✅ | | TypeScript types bundled | ✅ | 🛠️ DIY | ✅ |

If you've searched for a custom bottom tab bar, floating tab bar, or an animated tab bar and found options that only support Expo or bare React Native — this is one drop-in component that covers both, so you don't rewrite the bar when you switch navigators.

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-bar

Then 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-worklets

Make 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 set

React 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-worklets

Add 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.

Upgrading from 2.x

3.0 changes three visual defaults. Each has a one-line fix if you want the old behavior:

  • The bar is now content-sized and centered by default (see Bar width). To keep an edge-to-edge bar, add fullWidth:
    <MagicTabs tabs={tabs} fullWidth />
  • docked bars now add the safe-area bottom inset themselves. If you wrapped a docked bar in a bottom-edge SafeAreaView to lift it off the home indicator, remove that wrapper to avoid doubled spacing.
  • renderBackground no longer casts a drop shadow (a custom background — usually a blur view — provides its own fill). No action needed unless you relied on the shadow behind an opaque custom background.

See the CHANGELOG for the full list.

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} /> },
      ]}
    />
  );
}
  • name must match the route file in your app/ directory (e.g. index, search, profile).
  • href is where the tab navigates.
  • icon receives { focused, color, size } so you can swap glyphs/colors for the active state.

MagicTabs renders Expo Router's <Tabs> for you — put it straight in app/_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[].name must match a <Tab.Screen name>.
  • No href — that's an Expo Router concept; React Navigation navigates by name.
  • Every visual prop from MagicTabs (theme, showLabels, labelPosition, variant, fullWidth, isTransparent, renderBackground, haptics, onTabPress, …) works here too, plus all the Recipes below.

Icons are bring-your-own, exactly like the Expo entry — the icon render function is identical. This example uses react-native-vector-icons (the bare-RN counterpart of @expo/vector-icons). Bundle its fonts the CocoaPods way — not react-native-asset: on iOS the pod bundles the font files (just add a UIAppFonts entry to Info.plist), and on Android apply the package's fonts.gradle in app/build.gradle. The only Expo-specific props are glass (expo-glass-effect) and haptics (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 tabs array? The config is just data — build it however you like; only name and icon are required per tab.

Recipes

The recipes below are written with MagicTabs (Expo Router), but every prop shown also works on MagicTabBarNavigation — pass it the same way.

Bar width

The bar is sized by its tabs and centered — it grows with the number of tabs (and the active tab's label) up to the width left by theme.horizontalMargin, then stops. A two-tab bar is a compact pill rather than an edge-to-edge bar with a large gap in the middle. The width change is animated, so adding or removing a tab morphs the bar.

<MagicTabs tabs={tabs} />           // content-sized, centered (default)
<MagicTabs tabs={tabs} fullWidth /> // edge-to-edge, tabs spread evenly

Use theme.horizontalMargin to cap how wide it may ever get:

<MagicTabs tabs={tabs} theme={{ horizontalMargin: 48 }} /> // narrower ceiling

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 }, // dot

badge 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 background
  • glass uses expo-glass-effect on iOS 26+ and falls back to the translucent barColor elsewhere.
  • renderBackground renders any view behind the bar; when omitted, a solid barColor is used.

Light mode (compact bar)

A shorter, icon-only bar that floats higher and — like the normal bar — is sized by its tabs, so it stays a tight pill whatever the tab count. 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.

Accessibility

The bar is usable with VoiceOver and TalkBack out of the box — there is nothing to switch on.

Tabs are labelled. Each tab exposes its label as its accessible name, falling back to the route name when a tab has no label. The row is marked accessibilityRole="tablist" and each tab accessibilityRole="tab", so assistive tech announces "tab 2 of 5" rather than reading five anonymous buttons. The active tab is reported as selected.

Badges are announced. A badge is invisible to a screen reader, so its meaning is folded into the tab's accessible name — a count or string badge appends its value, and a dot badge (badge: true) announces "new item":

{ name: "alerts", href: "/alerts", label: "Alerts", badge: 3,    icon } // → "Alerts, 3"
{ name: "alerts", href: "/alerts", label: "Alerts", badge: 150,  icon } // → "Alerts, 99+"
{ name: "feed",   href: "/feed",   label: "Feed",   badge: true, icon } // → "Feed, new item"

Touch targets meet the platform minimums. Icons render smaller than the ~44pt (iOS HIG) / 48dp (Material) minimum, so tabs add vertical hitSlop to reach it without changing the visual layout. The extra area is vertical only, so neighbouring tabs never overlap.

Large text is respected, within limits. Labels and badge counts scale with the OS font size, capped so they can't blow out the bar's fixed height or overflow the badge bubble. Labels truncate to one line rather than wrapping.

Targeting tabs from an e2e suite? Prefer testID over the accessible name — the name is the visible label, which may collide with other text on screen.

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. | | fullWidth | boolean | false | Stretch the bar edge-to-edge and spread the tabs. Off by default — the bar is sized by its tabs. | | 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. | | testID | string? | testID for the tab, so e2e suites (Maestro, Detox, Appium…) can target it. Without one, tabs are reachable by their accessibility label — which is the visible label, and may collide with other text on screen. |

<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, fullWidth, isLight, lightBottomMargin, isTransparent, transparency, glass, renderBackground, haptics, onTabPress, onTabLongPress.

MagicNavigationTab is Omit<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.

defaultTabs is exported from the react-native-magic-tab-bar/default-tabs subpath (not the main entry), so the core adds zero runtime dependencies.

FAQ

How do I create a custom bottom tab bar in Expo Router?

Render MagicTabs in your app/_layout.tsx and pass a tabs array mapping each route to an icon and label. It replaces Expo Router's default tab bar without you writing any layout code — see the Quick start.

How do I replace the default tab bar in React Navigation?

Pass MagicTabBarNavigation to your navigator's tabBar prop: <Tab.Navigator tabBar={(props) => <MagicTabBarNavigation {...props} tabs={tabs} />}>. It works with @react-navigation/bottom-tabs v6 and v7 in a bare React Native CLI app — see React Navigation (bare React Native).

Does it work with both Expo Router and React Navigation?

Yes — that's the point. The same look ships as two entry points: MagicTabs for Expo Router and react-native-magic-tab-bar/react-navigation for bare React Native. You only pull in the framework you actually use; the other framework's dependencies are never installed.

Does it support the New Architecture (Fabric) and Reanimated 3?

Yes. Animations run on react-native-reanimated (v3+) with react-native-worklets, and the library is tested on the New Architecture (Fabric) on both iOS and Android.

How do I add a floating action button (FAB) to the tab bar?

Give any tab variant: "action" and it renders as a raised, circular center button using the theme's actionColor / actionIconColor — see Action (FAB) tab.

Does it support iOS Liquid Glass / a blur tab bar?

Yes. Turn on the glass prop for a translucent blur background, backed by native iOS Liquid Glass when expo-glass-effect is installed — see Glass, blur and transparency. On Android and bare RN it falls back gracefully.

Do I have to use a specific icon library?

No. Bring your own icons — pass any component from each tab's icon render function (@expo/vector-icons, react-native-vector-icons, SVGs, or your own). The core adds zero runtime dependencies and forces no icon library.

Is it written in TypeScript?

Yes — types are bundled, so there's no separate @types package to install.

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 android

Editing files in src/ hot-reloads in whichever example is running. (If a library edit doesn't show up, restart Metro with npx expo start --clear — a stale bundler cache serves the old library code.)

Tests

npm test          # unit tests: the pure logic in src/utils.ts (no RN runtime)
npm run typecheck
npm run e2e       # Maestro flows against the Expo example on a simulator
npm run e2e:measure  # asserts the bar's on-device geometry at every tab count

The Maestro suite is what covers the parts a unit test can't: it drives the example app at 1–5 tabs and checks the bar is content-sized, centred, and never wider than theme.horizontalMargin allows. See .maestro/README.md for setup and what each flow covers.

Building / publishing

Publishing is restricted to package maintainers.

npm run build     # react-native-builder-bob -> lib/ (ESM + d.ts)
npm publish

Roadmap

  • 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