# Tabs

> A set of layered sections of content—known as tab panels—that are displayed one at a time.

**BNA UI** — a React Native / Expo component library.
These components render through `react-native`, not the DOM: there are no HTML
elements, no Tailwind classes and no Radix primitives. Source is copied into your
project and imported through `@/components/ui/*`, `@/components/charts/*`,
`@/hooks/*` and `@/theme/*`. Colours come from the `useColor` hook rather than
hardcoded hex; sizing tokens (`HEIGHT`, `FONT_SIZE`, `BORDER_RADIUS`, `CORNERS`)
come from `@/theme/globals`.

- Docs: https://ui.ahmedbna.com/docs/components/tabs
- Markdown: https://ui.ahmedbna.com/docs/components/tabs.md
- Structured JSON (props, usage, source, examples): https://ui.ahmedbna.com/r/ai/tabs.json
- Install payload (source plus every file it imports): https://ui.ahmedbna.com/r/tabs.json
- Install: `npx bna-ui add tabs`
- npm dependencies: `expo-haptics`, `react-native-gesture-handler`, `react-native-reanimated`, `react-native-worklets`
- Registry dependencies: `mode-provider`, `useColorScheme`, `colors`, `useColor`, `useHaptics`, `globals`, `text`, `view`
- Preview recording: https://demo.ahmedbna.com/0299-tabs-demo.MP4

---

**Example:** A basic tabs component with multiple panels

```tsx
// components/demo/tabs/tabs-demo.tsx
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { Text } from '@/components/ui/text';
import { View } from '@/components/ui/view';
import React from 'react';

export function TabsDemo() {
  return (
    <Tabs defaultValue='account' style={{ width: 400 }}>
      <TabsList>
        <TabsTrigger value='account'>Account</TabsTrigger>
        <TabsTrigger value='followers'>Followers</TabsTrigger>
        <TabsTrigger value='following'>Following</TabsTrigger>
        <TabsTrigger value='password'>Password</TabsTrigger>
        <TabsTrigger value='settings'>Settings</TabsTrigger>
        <TabsTrigger value='more'>More</TabsTrigger>
      </TabsList>

      <TabsContent value='account'>
        <View style={{ paddingHorizontal: 16 }}>
          <Text variant='title' style={{ marginBottom: 8 }}>
            Account Settings
          </Text>
          <Text variant='body'>
            Manage your account information and preferences here.
          </Text>
        </View>
      </TabsContent>

      <TabsContent value='followers'>
        <View style={{ paddingHorizontal: 16 }}>
          <Text variant='title' style={{ marginBottom: 8 }}>
            Followers
          </Text>
          <Text variant='body'>
            Manage your followers information and preferences here.
          </Text>
        </View>
      </TabsContent>

      <TabsContent value='following'>
        <View style={{ paddingHorizontal: 16 }}>
          <Text variant='title' style={{ marginBottom: 8 }}>
            Following
          </Text>
          <Text variant='body'>
            Manage your following information and preferences here.
          </Text>
        </View>
      </TabsContent>

      <TabsContent value='password'>
        <View style={{ paddingHorizontal: 16 }}>
          <Text variant='title' style={{ marginBottom: 8 }}>
            Password Settings
          </Text>
          <Text variant='body'>
            Change your password and security settings preferences here.
          </Text>
        </View>
      </TabsContent>

      <TabsContent value='settings'>
        <View style={{ paddingHorizontal: 16 }}>
          <Text variant='title' style={{ marginBottom: 8 }}>
            General Settings
          </Text>
          <Text variant='body'>
            Configure your application preferences and options.
          </Text>
        </View>
      </TabsContent>

      <TabsContent value='more'>
        <View style={{ paddingHorizontal: 16 }}>
          <Text variant='title' style={{ marginBottom: 8 }}>
            More
          </Text>
          <Text variant='body'>
            Configure your application preferences and options.
          </Text>
        </View>
      </TabsContent>
    </Tabs>
  );
}
```

## Installation

### CLI

```bash
npx bna-ui add tabs
```

### Manual

**1.** Install the following dependencies:

```bash
npx expo install react-native
```

**2.** Copy and paste the following code into your project.

```tsx
// components/ui/tabs.tsx
import { Text } from '@/components/ui/text';
import { View } from '@/components/ui/view';
import { useColor } from '@/hooks/useColor';
import { useHaptics } from '@/hooks/useHaptics';
import { BORDER_RADIUS, CORNERS, FONT_SIZE, HEIGHT } from '@/theme/globals';
import React, {
  createContext,
  useCallback,
  useContext,
  useEffect,
  useRef,
  useState,
} from 'react';
import {
  ScrollView,
  TextStyle,
  TouchableOpacity,
  useWindowDimensions,
  ViewStyle,
} from 'react-native';
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
import Animated, {
  Extrapolation,
  interpolate,
  runOnJS,
  useAnimatedStyle,
  useSharedValue,
  withTiming,
} from 'react-native-reanimated';

// Types
interface TabsContextType {
  activeTab: string;
  setActiveTab: (value: string) => void;
  orientation: 'horizontal' | 'vertical';
  tabValues: string[];
  registerTab: (value: string) => void;
  unregisterTab: (value: string) => void;
  enableSwipe?: boolean;
  navigateToAdjacentTab?: (direction: 'next' | 'prev') => void;
  contentMap: React.MutableRefObject<Record<string, React.ReactNode>>;
  haptic?: boolean;
}

interface TabsProps {
  children: React.ReactNode;
  defaultValue?: string;
  value?: string;
  onValueChange?: (value: string) => void;
  orientation?: 'horizontal' | 'vertical';
  style?: ViewStyle;
  enableSwipe?: boolean;
  haptic?: boolean;
}

interface TabsListProps {
  children: React.ReactNode;
  style?: ViewStyle;
}

interface TabsTriggerProps {
  children: React.ReactNode;
  value: string;
  disabled?: boolean;
  style?: ViewStyle;
  textStyle?: TextStyle;
}

interface TabsContentProps {
  children: React.ReactNode;
  value: string;
  style?: ViewStyle;
}

// Context
const TabsContext = createContext<TabsContextType | undefined>(undefined);

const useTabsContext = () => {
  const context = useContext(TabsContext);
  if (!context) {
    throw new Error('Tabs components must be used within a Tabs provider');
  }
  return context;
};

export function Tabs({
  children,
  defaultValue = '',
  value,
  onValueChange,
  orientation = 'horizontal',
  style,
  enableSwipe = true,
  haptic = true,
}: TabsProps) {
  const feedback = useHaptics(haptic);
  const [internalActiveTab, setInternalActiveTab] = useState(defaultValue);
  const [tabValues, setTabValues] = useState<string[]>([]);
  // Per-instance carousel content cache — must live here (not module scope)
  // so two mounted Tabs never share/corrupt each other's content.
  const contentMap = useRef<Record<string, React.ReactNode>>({});

  // Determine if we're in controlled or uncontrolled mode
  const isControlled = value !== undefined;
  const activeTab = isControlled ? value : internalActiveTab;

  // Update internal state when value prop changes (controlled mode)
  useEffect(() => {
    if (isControlled && value !== internalActiveTab) {
      setInternalActiveTab(value);
    }
  }, [value, isControlled, internalActiveTab]);

  const setActiveTab = (newValue: string) => {
    if (!isControlled) {
      // Uncontrolled mode: update internal state
      setInternalActiveTab(newValue);
    }

    // Call onValueChange callback if provided (works in both controlled and uncontrolled modes)
    if (onValueChange) {
      onValueChange(newValue);
    }
  };

  const registerTab = useCallback((tabValue: string) => {
    setTabValues((prev) => {
      if (!prev.includes(tabValue)) {
        return [...prev, tabValue];
      }
      return prev;
    });
  }, []);

  const unregisterTab = useCallback((tabValue: string) => {
    setTabValues((prev) => prev.filter((val) => val !== tabValue));
  }, []);

  const navigateToAdjacentTab = useCallback(
    (direction: 'next' | 'prev') => {
      const currentIndex = tabValues.indexOf(activeTab);
      if (currentIndex === -1) return;

      let nextIndex;
      if (direction === 'next') {
        nextIndex = currentIndex + 1;
        if (nextIndex >= tabValues.length) nextIndex = 0; // Loop to first
      } else {
        nextIndex = currentIndex - 1;
        if (nextIndex < 0) nextIndex = tabValues.length - 1; // Loop to last
      }

      const nextTab = tabValues[nextIndex];
      if (nextTab) {
        // Fires here rather than in the pan gesture's onEnd, which is a worklet
        // on the UI thread — this runs on JS via the existing runOnJS hop. It
        // is also not in setActiveTab, which programmatic/controlled updates
        // also go through.
        feedback('selection');
        setActiveTab(nextTab);
      }
    },
    [tabValues, activeTab, setActiveTab, feedback]
  );

  return (
    <TabsContext.Provider
      value={{
        activeTab,
        setActiveTab,
        orientation,
        tabValues,
        registerTab,
        unregisterTab,
        enableSwipe,
        navigateToAdjacentTab,
        contentMap,
        haptic,
      }}
    >
      <View
        style={[
          {
            flexDirection: orientation === 'horizontal' ? 'column' : 'row',
          },
          style,
        ]}
      >
        {children}
      </View>
    </TabsContext.Provider>
  );
}

// Add this after the existing interfaces
interface CarouselTabContentProps {
  children: React.ReactNode;
  value: string;
  style?: ViewStyle;
}

function CarouselTabContent({
  children,
  value,
  style,
}: CarouselTabContentProps) {
  const { activeTab, navigateToAdjacentTab, tabValues, contentMap } =
    useTabsContext();

  // Store this content in the per-instance map (mutation during render,
  // matching the ref's intended "always current on next read" semantics —
  // must not trigger a re-render on write).
  contentMap.current[value] = children;

  // Only render the carousel container for the active tab
  if (activeTab !== value) {
    return null;
  }

  return (
    <CarouselContainer
      activeTab={activeTab}
      tabValues={tabValues}
      onSwipe={navigateToAdjacentTab!}
      contentMap={contentMap}
      style={style}
    />
  );
}

function CarouselContainer({
  activeTab,
  tabValues,
  onSwipe,
  contentMap,
  style,
}: {
  activeTab: string;
  tabValues: string[];
  onSwipe: (direction: 'next' | 'prev') => void;
  contentMap: React.MutableRefObject<Record<string, React.ReactNode>>;
  style?: ViewStyle;
}) {
  const { width: screenWidth } = useWindowDimensions();
  const translateX = useSharedValue(0);
  const isGestureActive = useSharedValue(false);
  const currentIndex = tabValues.indexOf(activeTab);

  // Reset translation when active tab changes (only if not during gesture)
  useEffect(() => {
    if (!isGestureActive.value) {
      translateX.value = withTiming(0, { duration: 300 });
    }
  }, [activeTab]);

  const panGesture = Gesture.Pan()
    .onBegin(() => {
      isGestureActive.value = true;
    })
    .onUpdate((event) => {
      translateX.value = event.translationX;
    })
    .onEnd((event) => {
      isGestureActive.value = false;

      const threshold = screenWidth * 0.15; // Lower threshold for easier swiping
      const velocity = Math.abs(event.velocityX);
      const translation = event.translationX;

      // Determine if we should change tabs based on distance or velocity
      const shouldChangeTab =
        Math.abs(translation) > threshold || velocity > 500;

      if (shouldChangeTab) {
        if (translation > 0 && currentIndex > 0) {
          // Swiped right - go to previous tab
          runOnJS(onSwipe)('prev');
        } else if (translation < 0 && currentIndex < tabValues.length - 1) {
          // Swiped left - go to next tab
          runOnJS(onSwipe)('next');
        }
      }

      // No snapping back - let the tab change handle the reset
    });

  const getPreviousTab = () => {
    const prevIndex = currentIndex - 1;
    return prevIndex >= 0 ? tabValues[prevIndex] : null;
  };

  const getNextTab = () => {
    const nextIndex = currentIndex + 1;
    return nextIndex < tabValues.length ? tabValues[nextIndex] : null;
  };

  const previousTab = getPreviousTab();
  const nextTab = getNextTab();

  const containerStyle = useAnimatedStyle(() => ({
    transform: [{ translateX: translateX.value }],
  }));

  const previousStyle = useAnimatedStyle(() => {
    const opacity = interpolate(
      translateX.value,
      [0, screenWidth * 0.5],
      [0, 1],
      Extrapolation.CLAMP
    );

    return {
      transform: [{ translateX: translateX.value - screenWidth }],
      opacity: previousTab ? opacity : 0,
    };
  });

  const nextStyle = useAnimatedStyle(() => {
    const opacity = interpolate(
      translateX.value,
      [-screenWidth * 0.5, 0],
      [1, 0],
      Extrapolation.CLAMP
    );

    return {
      transform: [{ translateX: translateX.value + screenWidth }],
      opacity: nextTab ? opacity : 0,
    };
  });

  return (
    <GestureDetector gesture={panGesture}>
      <View style={{ overflow: 'hidden' }}>
        {/* Previous content */}
        {previousTab && (
          <Animated.View
            style={[
              {
                position: 'absolute',
                width: screenWidth,
                paddingTop: 16,
              },
              style,
              previousStyle,
            ]}
            pointerEvents='none'
          >
            {contentMap.current[previousTab]}
          </Animated.View>
        )}

        {/* Current content */}
        <Animated.View
          style={[
            {
              paddingTop: 16,
            },
            style,
            containerStyle,
          ]}
        >
          {contentMap.current[activeTab]}
        </Animated.View>

        {/* Next content */}
        {nextTab && (
          <Animated.View
            style={[
              {
                position: 'absolute',
                width: screenWidth,
                paddingTop: 16,
              },
              style,
              nextStyle,
            ]}
            pointerEvents='none'
          >
            {contentMap.current[nextTab]}
          </Animated.View>
        )}
      </View>
    </GestureDetector>
  );
}

export function TabsList({ children, style }: TabsListProps) {
  const { orientation } = useTabsContext();
  const backgroundColor = useColor('muted');

  return (
    <View
      accessibilityRole='tablist'
      style={[
        {
          padding: 6,
          backgroundColor,
          borderRadius: orientation === 'horizontal' ? CORNERS : BORDER_RADIUS,
        },
        style,
      ]}
    >
      <ScrollView
        horizontal={orientation === 'horizontal'}
        showsHorizontalScrollIndicator={false}
        showsVerticalScrollIndicator={false}
        contentContainerStyle={{
          flexDirection: orientation === 'horizontal' ? 'row' : 'column',
          alignItems: 'center',
        }}
      >
        {children}
      </ScrollView>
    </View>
  );
}

export function TabsTrigger({
  children,
  value,
  disabled = false,
  style,
  textStyle,
}: TabsTriggerProps) {
  const {
    activeTab,
    setActiveTab,
    orientation,
    registerTab,
    unregisterTab,
    haptic,
  } = useTabsContext();
  const isActive = activeTab === value;
  const feedback = useHaptics(haptic ?? true);

  // Register/unregister tab for swipe navigation
  useEffect(() => {
    registerTab(value);
    return () => unregisterTab(value);
  }, [value, registerTab, unregisterTab]);

  const primaryColor = useColor('primary');
  const mutedForegroundColor = useColor('mutedForeground');
  const backgroundColor = useColor('background');

  const handlePress = () => {
    if (!disabled) {
      if (!isActive) feedback('selection');
      setActiveTab(value);
    }
  };

  const triggerStyle: ViewStyle = {
    paddingHorizontal: 12,
    paddingVertical: orientation === 'vertical' ? 8 : undefined,
    borderRadius: CORNERS,
    alignItems: 'center',
    justifyContent: 'center',
    minHeight: HEIGHT - 8,
    backgroundColor: isActive ? backgroundColor : 'transparent',
    opacity: disabled ? 0.5 : 1,
    flex: orientation === 'horizontal' ? 1 : undefined,
    marginBottom: orientation === 'vertical' ? 4 : 0,
    ...style,
  };

  const triggerTextStyle: TextStyle = {
    fontSize: FONT_SIZE,
    fontWeight: '500',
    color: isActive ? primaryColor : mutedForegroundColor,
    textAlign: 'center',
    ...textStyle,
  };

  return (
    <TouchableOpacity
      style={triggerStyle}
      onPress={handlePress}
      disabled={disabled}
      activeOpacity={0.8}
      accessibilityRole='tab'
      accessibilityState={{ selected: isActive, disabled }}
    >
      {typeof children === 'string' ? (
        <Text style={triggerTextStyle}>{children}</Text>
      ) : (
        children
      )}
    </TouchableOpacity>
  );
}

export function TabsContent({ children, value, style }: TabsContentProps) {
  const {
    activeTab,
    enableSwipe,
    orientation,
    navigateToAdjacentTab,
    tabValues,
  } = useTabsContext();
  const isActive = activeTab === value;

  // For carousel mode, we need to render all content but only show active one
  if (enableSwipe && orientation === 'horizontal' && navigateToAdjacentTab) {
    return (
      <CarouselTabContent value={value} style={style}>
        {children}
      </CarouselTabContent>
    );
  }

  // Regular mode - only render active content
  if (!isActive) {
    return null;
  }

  return (
    <View
      style={[
        {
          paddingTop: 16,
        },
        style,
      ]}
    >
      {children}
    </View>
  );
}
```

**3.** Update the import paths to match your project setup.

## Usage

```tsx
import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs';
```

```tsx
<Tabs defaultValue='tab1'>
  <TabsList>
    <TabsTrigger value='tab1'>Tab 1</TabsTrigger>
    <TabsTrigger value='tab2'>Tab 2</TabsTrigger>
    <TabsTrigger value='tab3'>Tab 3</TabsTrigger>
  </TabsList>
  <TabsContent value='tab1'>
    <Text>Content for Tab 1</Text>
  </TabsContent>
  <TabsContent value='tab2'>
    <Text>Content for Tab 2</Text>
  </TabsContent>
  <TabsContent value='tab3'>
    <Text>Content for Tab 3</Text>
  </TabsContent>
</Tabs>
```

## Examples

#### Default

**Example:** A basic tabs component with multiple panels

```tsx
// components/demo/tabs/tabs-demo.tsx
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { Text } from '@/components/ui/text';
import { View } from '@/components/ui/view';
import React from 'react';

export function TabsDemo() {
  return (
    <Tabs defaultValue='account' style={{ width: 400 }}>
      <TabsList>
        <TabsTrigger value='account'>Account</TabsTrigger>
        <TabsTrigger value='followers'>Followers</TabsTrigger>
        <TabsTrigger value='following'>Following</TabsTrigger>
        <TabsTrigger value='password'>Password</TabsTrigger>
        <TabsTrigger value='settings'>Settings</TabsTrigger>
        <TabsTrigger value='more'>More</TabsTrigger>
      </TabsList>

      <TabsContent value='account'>
        <View style={{ paddingHorizontal: 16 }}>
          <Text variant='title' style={{ marginBottom: 8 }}>
            Account Settings
          </Text>
          <Text variant='body'>
            Manage your account information and preferences here.
          </Text>
        </View>
      </TabsContent>

      <TabsContent value='followers'>
        <View style={{ paddingHorizontal: 16 }}>
          <Text variant='title' style={{ marginBottom: 8 }}>
            Followers
          </Text>
          <Text variant='body'>
            Manage your followers information and preferences here.
          </Text>
        </View>
      </TabsContent>

      <TabsContent value='following'>
        <View style={{ paddingHorizontal: 16 }}>
          <Text variant='title' style={{ marginBottom: 8 }}>
            Following
          </Text>
          <Text variant='body'>
            Manage your following information and preferences here.
          </Text>
        </View>
      </TabsContent>

      <TabsContent value='password'>
        <View style={{ paddingHorizontal: 16 }}>
          <Text variant='title' style={{ marginBottom: 8 }}>
            Password Settings
          </Text>
          <Text variant='body'>
            Change your password and security settings preferences here.
          </Text>
        </View>
      </TabsContent>

      <TabsContent value='settings'>
        <View style={{ paddingHorizontal: 16 }}>
          <Text variant='title' style={{ marginBottom: 8 }}>
            General Settings
          </Text>
          <Text variant='body'>
            Configure your application preferences and options.
          </Text>
        </View>
      </TabsContent>

      <TabsContent value='more'>
        <View style={{ paddingHorizontal: 16 }}>
          <Text variant='title' style={{ marginBottom: 8 }}>
            More
          </Text>
          <Text variant='body'>
            Configure your application preferences and options.
          </Text>
        </View>
      </TabsContent>
    </Tabs>
  );
}
```

#### Vertical Orientation

**Example:** Tabs arranged in vertical orientation

```tsx
// components/demo/tabs/tabs-vertical.tsx
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { Text } from '@/components/ui/text';
import { View } from '@/components/ui/view';
import React from 'react';

export function TabsVertical() {
  return (
    <Tabs defaultValue='profile' orientation='vertical'>
      <TabsList>
        <TabsTrigger value='profile'>🧑‍💼</TabsTrigger>
        <TabsTrigger value='security'>🫆</TabsTrigger>
        <TabsTrigger value='notifications'>🔔</TabsTrigger>
        <TabsTrigger value='billing'>💰</TabsTrigger>
      </TabsList>

      <TabsContent value='profile' style={{ flex: 1 }}>
        <View style={{ marginHorizontal: 16 }}>
          <Text variant='title' style={{ marginBottom: 8 }}>
            Profile Information
          </Text>
          <Text variant='body'>
            Update your personal information and profile picture.
          </Text>
        </View>
      </TabsContent>

      <TabsContent value='security' style={{ flex: 1 }}>
        <View style={{ marginHorizontal: 16 }}>
          <Text variant='title' style={{ marginBottom: 8 }}>
            Security Settings
          </Text>
          <Text variant='body'>
            Manage two-factor authentication and login security.
          </Text>
        </View>
      </TabsContent>

      <TabsContent value='notifications' style={{ flex: 1 }}>
        <View style={{ marginHorizontal: 16 }}>
          <Text variant='title' style={{ marginBottom: 8 }}>
            Notification Preferences
          </Text>
          <Text variant='body'>
            Configure how and when you receive notifications.
          </Text>
        </View>
      </TabsContent>

      <TabsContent value='billing' style={{ flex: 1 }}>
        <View style={{ marginHorizontal: 16 }}>
          <Text variant='title' style={{ marginBottom: 8 }}>
            Billing & Subscription
          </Text>
          <Text variant='body'>
            Manage your subscription and payment methods.
          </Text>
        </View>
      </TabsContent>
    </Tabs>
  );
}
```

#### Disabled Tabs

**Example:** Tabs with disabled states

```tsx
// components/demo/tabs/tabs-disabled.tsx
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { Text } from '@/components/ui/text';
import { View } from '@/components/ui/view';
import React from 'react';

export function TabsDisabled() {
  return (
    <Tabs defaultValue='available' style={{ width: 400 }}>
      <TabsList>
        <TabsTrigger value='available'>Available</TabsTrigger>
        <TabsTrigger value='pending'>Pending</TabsTrigger>
        <TabsTrigger value='premium' disabled>
          Premium
        </TabsTrigger>
        <TabsTrigger value='enterprise' disabled>
          Enterprise
        </TabsTrigger>
      </TabsList>

      <TabsContent value='available'>
        <View style={{ padding: 16 }}>
          <Text variant='title' style={{ marginBottom: 8 }}>
            Available Features
          </Text>
          <Text variant='body'>
            These features are currently available to you.
          </Text>
        </View>
      </TabsContent>

      <TabsContent value='pending'>
        <View style={{ padding: 16 }}>
          <Text variant='title' style={{ marginBottom: 8 }}>
            Pending Features
          </Text>
          <Text variant='body'>
            These features are being processed and will be available soon.
          </Text>
        </View>
      </TabsContent>

      <TabsContent value='premium'>
        <View style={{ padding: 16 }}>
          <Text variant='title' style={{ marginBottom: 8 }}>
            Premium Features
          </Text>
          <Text variant='body'>Upgrade to access premium features.</Text>
        </View>
      </TabsContent>

      <TabsContent value='enterprise'>
        <View style={{ padding: 16 }}>
          <Text variant='title' style={{ marginBottom: 8 }}>
            Enterprise Features
          </Text>
          <Text variant='body'>Contact sales for enterprise features.</Text>
        </View>
      </TabsContent>
    </Tabs>
  );
}
```

#### Custom Styling

**Example:** Tabs with custom colors and styling

```tsx
// components/demo/tabs/tabs-styled.tsx
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { Text } from '@/components/ui/text';
import { View } from '@/components/ui/view';
import { useState } from 'react';

export function TabsStyled() {
  const [value, setValue] = useState('design');

  return (
    <Tabs value={value} onValueChange={setValue}>
      <TabsList
        style={{
          backgroundColor:
            value === 'design'
              ? '#3b82f6'
              : value === 'development'
                ? '#10b981'
                : '#f59e0b',
          shadowColor: '#000',
          shadowOffset: { width: 0, height: 2 },
          shadowOpacity: 0.1,
          shadowRadius: 4,
          elevation: 3,
          borderRadius: 8,
        }}
      >
        <TabsTrigger
          value='design'
          style={{ borderRadius: 8 }}
          textStyle={{ fontWeight: '600', color: '#3b82f6' }}
        >
          Design
        </TabsTrigger>
        <TabsTrigger
          value='development'
          style={{ borderRadius: 8 }}
          textStyle={{ fontWeight: '600', color: '#10b981' }}
        >
          Development
        </TabsTrigger>
        <TabsTrigger
          value='testing'
          style={{ borderRadius: 8 }}
          textStyle={{ fontWeight: '600', color: '#f59e0b' }}
        >
          Testing
        </TabsTrigger>
      </TabsList>

      <TabsContent value='design'>
        <View
          style={{
            padding: 20,
            backgroundColor: '#eff6ff',
            borderRadius: 12,
            marginTop: 8,
          }}
        >
          <Text variant='title' style={{ color: '#1e40af', marginBottom: 8 }}>
            Design Phase
          </Text>
          <Text variant='body' style={{ color: '#1e40af' }}>
            Create wireframes, mockups, and design systems for your project.
          </Text>
        </View>
      </TabsContent>

      <TabsContent value='development'>
        <View
          style={{
            padding: 20,
            backgroundColor: '#ecfdf5',
            borderRadius: 12,
            marginTop: 8,
          }}
        >
          <Text variant='title' style={{ color: '#047857', marginBottom: 8 }}>
            Development Phase
          </Text>
          <Text variant='body' style={{ color: '#047857' }}>
            Build and implement the features based on the design specifications.
          </Text>
        </View>
      </TabsContent>

      <TabsContent value='testing'>
        <View
          style={{
            padding: 20,
            backgroundColor: '#fffbeb',
            borderRadius: 12,
            marginTop: 8,
          }}
        >
          <Text variant='title' style={{ color: '#92400e', marginBottom: 8 }}>
            Testing Phase
          </Text>
          <Text variant='body' style={{ color: '#92400e' }}>
            Perform quality assurance and user acceptance testing.
          </Text>
        </View>
      </TabsContent>
    </Tabs>
  );
}
```

## API Reference

### Tabs

The root container for the tabs component.

| Prop            | Type                         | Default        | Description                                                                                                                         |
| --------------- | ---------------------------- | -------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| `haptic`        | `boolean`                    | `true`         | Whether to trigger haptic feedback when the active tab changes, by tap or by swipe. Programmatic changes stay silent.               |
| `children`      | `ReactNode`                  | -              | The tabs list and content components.                                                                                               |
| `defaultValue`  | `string`                     | -              | The value of the tab that should be active by default.                                                                              |
| `value`         | `string`                     | -              | The controlled active tab value. When provided, the component becomes controlled and `onValueChange` must be used to update it.     |
| `onValueChange` | `(value: string) => void`    | -              | Called whenever the active tab changes, whether by press or swipe.                                                                  |
| `orientation`   | `'horizontal' \| 'vertical'` | `'horizontal'` | The orientation of the tabs.                                                                                                        |
| `enableSwipe`   | `boolean`                    | `true`         | Whether horizontal tabs can be swiped between, in addition to pressing a trigger. Has no effect when `orientation` is `"vertical"`. |
| `style`         | `ViewStyle`                  | -              | Additional styles to apply to the container.                                                                                        |

### TabsList

Container for the tab triggers.

| Prop       | Type        | Description                                  |
| ---------- | ----------- | -------------------------------------------- |
| `children` | `ReactNode` | The tab trigger components.                  |
| `style`    | `ViewStyle` | Additional styles to apply to the tabs list. |

### TabsTrigger

The clickable tab that activates its associated content.

| Prop        | Type        | Default | Description                                     |
| ----------- | ----------- | ------- | ----------------------------------------------- |
| `children`  | `ReactNode` | -       | The content of the tab trigger (usually text).  |
| `value`     | `string`    | -       | The unique value that identifies this tab.      |
| `disabled`  | `boolean`   | `false` | Whether the tab is disabled.                    |
| `style`     | `ViewStyle` | -       | Additional styles to apply to the trigger.      |
| `textStyle` | `TextStyle` | -       | Additional styles to apply to the trigger text. |

### TabsContent

The content panel associated with a tab trigger.

| Prop       | Type        | Description                                    |
| ---------- | ----------- | ---------------------------------------------- |
| `children` | `ReactNode` | The content to display when the tab is active. |
| `value`    | `string`    | The value that matches the associated trigger. |
| `style`    | `ViewStyle` | Additional styles to apply to the content.     |

## Accessibility

The Tabs component is built with accessibility in mind:

- `TabsList` exposes `accessibilityRole="tablist"`, each `TabsTrigger` exposes `accessibilityRole="tab"` with `accessibilityState={{ selected, disabled }}`
- Disabled tabs report `accessibilityState.disabled` to screen readers
