Animated tabs with a sliding indicator in React Native
By the LumioUI team · Updated
To animate tabs in React Native, measure each tab with onLayout. Then drive one indicator's position and width from Reanimated shared values, and spring them to the selected tab. The label colours and the panel below should follow the same selection. The LumioUI Tabs component does this for you, as a pill or an underline.
Live demo of the LumioUI Tabs component. It's interactive, so try it.
How a sliding tab indicator works
- Measure. Each tab reports its
xandwidththroughonLayout. Store them in a ref, because re-rendering on every measurement is wasted work. - Move one indicator. A single absolutely positioned view sits behind the labels. Animate its
translateXandwidthwith a spring, so it glides and stretches between tabs of different widths. - Swap the panel. Fade the old panel out as the new one fades in. Keep the exit shorter than the entrance so the switch feels quick.
- Accessibility. Tabs need
accessibilityRole="tab"andselectedstate, so screen readers announce "Month, tab, selected".
import * as React from "react";
import { LayoutChangeEvent, Pressable, Text, View } from "react-native";
import Animated, { useAnimatedStyle, useSharedValue, withSpring } from "react-native-reanimated";
export function SimpleTabs({ tabs, value, onChange }) {
const x = useSharedValue(0);
const w = useSharedValue(0);
const layouts = React.useRef<Record<string, { x: number; width: number }>>({});
const moveTo = (key: string) => {
const l = layouts.current[key];
if (!l) return;
x.value = withSpring(l.x, { damping: 20, stiffness: 260 });
w.value = withSpring(l.width, { damping: 20, stiffness: 260 });
};
React.useEffect(() => moveTo(value), [value]);
const indicator = useAnimatedStyle(() => ({ width: w.value, transform: [{ translateX: x.value }] }));
return (
<View style={{ flexDirection: "row" }}>
<Animated.View style={[{ position: "absolute", top: 0, bottom: 0, borderRadius: 999, backgroundColor: "#111" }, indicator]} />
{tabs.map((t) => (
<Pressable
key={t}
accessibilityRole="tab"
accessibilityState={{ selected: t === value }}
onLayout={(e: LayoutChangeEvent) => {
layouts.current[t] = e.nativeEvent.layout;
if (t === value) moveTo(t);
}}
onPress={() => onChange(t)}
style={{ paddingHorizontal: 16, paddingVertical: 8 }}
>
<Text style={{ color: t === value ? "#fff" : "#666" }}>{t}</Text>
</Pressable>
))}
</View>
);
} From here you'd still add reduced-motion support (jump instead of glide), label colours that change with the indicator, the panel transition, and haptics on change.
The quick way: LumioUI Tabs
The Tabs component's indicator glides to the chosen tab, the label colour follows it, and the new panel fades up as the old one fades away. Choose variant="pill" or "line". It's free and runs in Expo Go.
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs/tabs";
import { Text } from "@/components/ui/text/text";
export function Spending() {
return (
<Tabs defaultValue="week" variant="pill">
<TabsList>
<TabsTrigger value="week">Week</TabsTrigger>
<TabsTrigger value="month">Month</TabsTrigger>
<TabsTrigger value="year">Year</TabsTrigger>
</TabsList>
<TabsContent value="week"><Text>$214 this week</Text></TabsContent>
<TabsContent value="month"><Text>$1,032 this month</Text></TabsContent>
<TabsContent value="year"><Text>$11,870 this year</Text></TabsContent>
</Tabs>
);
} valueandonValueChangemake it controlled. UsedefaultValueto let it manage itself.TabsTriggertakes aniconanddisabled.- For a bottom navigation bar instead of in-page tabs, see Dock (Pro). For two or three options, see Segmented Control.
Add it to your app
LumioUI's CLI copies the component's source into your project, so it's yours to read and change. Run init once per project:
npx @lumioui/cli init
npx @lumioui/cli add tabs It works in Expo (SDK 54+) and bare React Native, with Reanimated 4 and NativeWind. See the installation guide and the full Tabs API.
Related components
- Segmented Control
- Toggle Group
- Dock (Pro)
- Scroll Spy Tabs (Pro)
More React Native guides
- How to make a bottom sheet in React Native
- How to show toast notifications in React Native
- Skeleton loading screens in React Native
- How to add dark mode and a theme toggle in React Native
- How to build an accordion in React Native
- How to make a side drawer menu in React Native
- How to make an animated heart like button in React Native
- How to make a modal dialog in React Native
- How to build an animated carousel in React Native
- How to make an animated splash screen in React Native and Expo
- How to build a month calendar in React Native