AniUI

Tabs

Tab navigation with animated indicator for switching between content panels.

Account settings content here.

import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs";

export function MyScreen() {
  return (
    <Tabs defaultValue="account">
      <TabsList>
        <TabsTrigger value="account">Account</TabsTrigger>
        <TabsTrigger value="password">Password</TabsTrigger>
      </TabsList>
      <TabsContent value="account">
        <Text>Account settings content here.</Text>
      </TabsContent>
      <TabsContent value="password">
        <Text>Password settings content here.</Text>
      </TabsContent>
    </Tabs>
  );
}

Installation

npx @aniui/cli add tabs

This component requires react-native-reanimated for the tab indicator animation.

Usage

app/index.tsx
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs";

export function MyScreen() {
  return (
    <Tabs defaultValue="account">
      <TabsList>
        <TabsTrigger value="account">Account</TabsTrigger>
        <TabsTrigger value="password">Password</TabsTrigger>
      </TabsList>
      <TabsContent value="account">
        <Text>Account settings content here.</Text>
      </TabsContent>
      <TabsContent value="password">
        <Text>Password settings content here.</Text>
      </TabsContent>
    </Tabs>
  );
}

Compound Components

ComponentDescription
Tabs

Root container managing tab state

TabsList

Container for tab triggers

TabsTrigger

Pressable tab button with animated background

TabsContent

Content panel shown when tab is active

Props

Tabs

PropTypeDefault
defaultValue
string
required
className
string

TabsTrigger

PropTypeDefault
value
string
required
className
string

TabsContent

PropTypeDefault
value
string
required
className
string

Source

components/ui/tabs.tsx
import React, { createContext, useContext, useState } from "react";
import { View, Pressable, Text } from "react-native";
import Animated, { useAnimatedStyle, useSharedValue, withTiming } from "react-native-reanimated";
import { cn } from "@/lib/utils";

const TabsCtx = createContext<{ value: string; onValueChange: (v: string) => void }>({ value: "", onValueChange: () => {} });
export interface TabsProps extends React.ComponentPropsWithoutRef<typeof View> {
  className?: string;
  defaultValue: string;
  children?: React.ReactNode;
}
export function Tabs({ defaultValue, className, children, ...props }: TabsProps) {
  const [value, setValue] = useState(defaultValue);
  return (
    <TabsCtx.Provider value={{ value, onValueChange: setValue }}>
      <View className={cn("", className)} {...props}>{children}</View>
    </TabsCtx.Provider>
  );
}
export interface TabsListProps extends React.ComponentPropsWithoutRef<typeof View> {
  className?: string;
  children?: React.ReactNode;
}
export function TabsList({ className, ...props }: TabsListProps) {
  return <View className={cn("flex-row rounded-lg bg-muted p-1", className)} {...props} />;
}
export interface TabsTriggerProps extends React.ComponentPropsWithoutRef<typeof Pressable> {
  className?: string;
  value: string;
  children: React.ReactNode;
}
export function TabsTrigger({ value, className, children, ...props }: TabsTriggerProps) {
  const { value: selected, onValueChange } = useContext(TabsCtx);
  const isActive = selected === value;
  const opacity = useSharedValue(isActive ? 1 : 0);
  React.useEffect(() => { opacity.value = withTiming(isActive ? 1 : 0, { duration: 150 }); }, [isActive, opacity]);
  const bgStyle = useAnimatedStyle(() => ({ position: "absolute" as const, inset: 0, borderRadius: 6, backgroundColor: "hsl(0, 0%, 100%)", opacity: opacity.value }));
  return (
    <Pressable className={cn("flex-1 items-center justify-center py-2 min-h-12 relative", className)} onPress={() => onValueChange(value)} accessible={true} accessibilityRole="tab" accessibilityState={{ selected: isActive }} {...props}>
      <Animated.View style={bgStyle} />
      {typeof children === "string" ? <Text className={cn("text-sm font-medium", isActive ? "text-foreground" : "text-muted-foreground")}>{children}</Text> : children}
    </Pressable>
  );
}
export interface TabsContentProps extends React.ComponentPropsWithoutRef<typeof View> {
  className?: string;
  value: string;
  children?: React.ReactNode;
}
export function TabsContent({ value, className, ...props }: TabsContentProps) {
  const { value: selected } = useContext(TabsCtx);
  if (selected !== value) return null;
  return <View className={cn("mt-2", className)} {...props} />;
}