Skip to content
Native Animation

React Native Bottom Navigation Animation with Animated Tabs

By Native Animation · Updated

React Native animated tabs can represent three different tasks: moving between app destinations, jumping between sections of one screen, or changing a local view. Choose that task before choosing an indicator animation. A moving capsule cannot fix a navigation model that loses the active destination or changes selection while someone is still reading.

This guide compares published catalog patterns and shows how to connect their visual state to your application. It complements the glass tab bar tutorial, which focuses on material and scroll-driven collapse rather than deciding which navigation pattern belongs in a screen.

What you'll build

Plan a tab surface with stable item IDs, one authoritative selection, and a clear relationship between that selection and visible content. For app navigation, the selected ID follows the route. For local filtering, it follows the filter. For section browsing, it follows the section currently being viewed, with deliberate handling of programmatic scrolling.

The animation explains a selection that has already happened. It should never postpone navigation until a spring finishes or leave two destinations looking selected. Start with readable labels and ordinary taps, then add motion to the indicator without changing the underlying interaction.

Prerequisites and compatibility

The catalog targets Expo SDK 58 and React Native 0.88.0. These patterns use Reanimated 4.7.1; pieces with UI-to-JavaScript callbacks also list react-native-worklets 0.13.0. The glass variant adds Expo Blur and Expo Linear Gradient. Check the dependency table on the selected piece instead of installing every package used anywhere in the family.

Use the SDK 58 reference for version-specific setup. Each published piece is reviewed on physical iPhone and Android devices with Expo Go before release; check its Compatibility section for platform-specific behavior. A working browser preview is evidence for that preview, not a test of native navigation, Android blur, or a screen reader on an iPhone.

Choose tabs by task

TaskPublished patternWhy choose it
Move between primary destinationsMorphing Tab BarAn active capsule stretches between icon-and-label destinations in a bottom bar.
Keep navigation available over a scrolling screenCollapsing Glass Tab BarA floating material surface changes geometry as content scrolls.
Jump through sections of one long listSynced Side TabsVertical tabs track measured section positions and manual scrolling.
Switch a local filter or viewSliding Segmented ControlA compact selected segment stays beside the content it changes.

Bottom navigation expresses separate destinations. Side tabs express positions inside a collection. A segmented control expresses a choice within the current context. Reusing one visual treatment for all three can make back behavior and selected state ambiguous, even when the motion looks consistent.

Expo Router bottom navigation animation

Expo Router's Tabs layout owns the route selection. Pass a custom tabBar to render your animated surface, then connect the bar's selection callback to the navigator's tab events so the router remains authoritative. Keep the visual indicator driven by the focused route rather than a second local navigation state:

import { Tabs } from 'expo-router';
import type { BottomTabBarProps } from 'expo-router/js-tabs';
import MorphingTabBar from './morphing-tab-bar';

function AnimatedTabBar({ state, descriptors, emitter, navigateToTab }: BottomTabBarProps) {
  const items = state.routes.map((route) => ({
    id: route.key,
    label: descriptors[route.key].options.title ?? route.name,
  }));
  const selectRoute = (key: string) => {
    const route = state.routes.find((item) => item.key === key);
    if (!route) return;
    const event = emitter.emit({ type: 'tabPress', target: key, canPreventDefault: true });
    if (!event.defaultPrevented) navigateToTab(route.key);
  };

  return <MorphingTabBar items={items} value={state.routes[state.index].key}
    onChange={selectRoute} />;
}

export default function TabLayout() {
  return <Tabs tabBar={(props) => <AnimatedTabBar {...props} />} />;
}

This uses the Expo Router Tabs navigator and the custom tabBar contract documented for the SDK 58 router. Keep the component outside src/app when it is reusable, and preserve the default tab accessibility labels or provide equivalent labels in your custom bar.

Connect the selection to application state

For Morphing Tab Bar, the public API accepts items, a controlled value, and defaultValue for an uncontrolled initial selection. Use stable string IDs that survive label changes. Avoid numeric indexes as application identifiers: reordering tabs should not move a saved selection to a different destination.

For route navigation, your route state remains authoritative. The bar presents it and your application handles destination changes. Read the Expo Router introduction before connecting custom visuals to a route layout. Keep presentation components outside the route directory, and verify the callback contract in the installed source and README.

For a local segmented choice, selection can stay in the screen's state. Update the displayed data and selected segment together. If fetching new data takes time, preserve the chosen segment and show a loading state in the content area rather than snapping back to the previous selection.

Try a controlled selection

After adding the piece, a local four-tab presentation can use its controlled contract. Import MorphingTabBar from the folder created by the CLI:

npx nativeanimation add morphing-tab-bar

Place the example beside the generated piece folder, or adjust the relative import to its location in your project.

import MorphingTabBar from './morphing-tab-bar';
import { useState } from 'react';

const destinations = [
  { id: 'home', label: 'Home' },
  { id: 'discover', label: 'Discover' },
  { id: 'saved', label: 'Saved' },
  { id: 'profile', label: 'Profile' },
];

export function LocalTabs() {
  const [selected, setSelected] = useState('home');
  return (
    <MorphingTabBar
      items={destinations}
      value={selected}
      onChange={setSelected}
    />
  );
}

This is a selection example, not a router implementation. Render the corresponding local content from the same selected ID. In a routed application, replace the local state with the active destination and connect onChange to your navigation layer.

Handle scroll synchronization deliberately

Section tabs need a different contract from destination tabs. Synced Side Tabs measures section positions and preserves a requested selection while the list scrolls there. This prevents intermediate scroll events from immediately overriding the tab someone just pressed.

Test the final section separately. A short last section may never reach the top of the viewport, so selection logic based only on top alignment can leave the previous tab active forever. Also test changing font size, inserting data, and returning to a screen whose list already has a scroll offset.

Keep the list and the tab model keyed to the same sections. Removing a section must remove its tab and resolve any selection pointing to it. Animation should follow the resolved state; it should not retain a label for content that no longer exists.

Accessibility and reduced motion

The selected state needs a semantic representation as well as color. Keep full spoken labels when icons are decorative, provide comfortable hit areas, and ensure the active destination remains clear with motion disabled. Test large text before shortening labels: abbreviated navigation can become harder to understand than a slightly taller bar.

With Reduce Motion enabled, keep selection changes immediate and use a still highlight or short fade. Avoid continuous stretching, large travel, and scroll-driven resizing. Changing the motion treatment must preserve every destination and the same selected item.

For local controls, test reading order between the selector and updated content. For navigation, test back behavior and focus after a destination change. An animated indicator is decorative; it should not become a second focusable copy of the selected tab.

Performance and layout

Measure the host surface rather than assuming a phone width. Insets, tablet layouts, split views, and translated labels change available space. An indicator that clips at the first or last tab is a geometry problem, not a reason to increase animation duration.

Keep per-frame indicator work in the animation layer. React should own discrete selection and content changes. Profile glass over the actual background; a static preview cannot predict the cost of blur over a scrolling video feed.

Common pitfalls

  • Keeping a second active route in the bar. A deep link or back action can make the indicator disagree with the screen.
  • Waiting for motion before switching content. Repeated taps become queued navigation rather than direct selection.
  • Using tabs for sequential setup. Next and Back usually explain a required sequence more clearly.
  • Letting scrolling override a requested section immediately. Preserve the requested destination until programmatic scrolling resolves.
  • Shrinking hit areas when the glass bar collapses. Visual density should not remove reachable actions.

Skip the build

Start with Morphing Tab Bar for a bottom-navigation presentation, then compare the task-specific choices above. Use the CLI guide to add source and its README. The application still owns routes, data loading, and back behavior.

FAQ

Which animated tabs should I use for a long section list?

Choose a section-synchronized pattern such as Synced Side Tabs. It connects tab selection to list position instead of pretending each section is a separate app destination.

Do animated tabs provide routing?

A visual tab component does not define your app's navigation model. Connect its documented selection API to the routing or local state your application already owns.

Are these patterns verified on every platform?

Each published piece is reviewed on physical iPhone and Android devices with Expo Go before release. Still test in your own app: dependency versions and successful native bundles do not certify device gestures, blur appearance, or assistive technology behavior.