Back to Heroui

Tabs

apps/docs/content/docs/native/components/(navigation)/tabs.mdx

3.0.525.2 KB
Original Source

<NativeVideoPlayerView srcLight="https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/docs/native/components/videos/tabs-docs-light.mp4" srcDark="https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/docs/native/components/videos/tabs-docs-dark.mp4" />

Import

tsx
import { Tabs } from 'heroui-native';

Anatomy

tsx
<Tabs>
  <Tabs.List>
    <Tabs.ScrollView>
      <Tabs.Indicator />
      <Tabs.Trigger>
        <Tabs.Label>...</Tabs.Label>
      </Tabs.Trigger>
      <Tabs.Separator />
      <Tabs.Trigger>
        <Tabs.Label>...</Tabs.Label>
      </Tabs.Trigger>
    </Tabs.ScrollView>
  </Tabs.List>
  <Tabs.Content>...</Tabs.Content>
</Tabs>
  • Tabs: Main container that manages tab state and selection. Controls active tab, handles value changes, and provides context to child components.
  • Tabs.List: Container for tab triggers. Groups triggers together with optional styling variants (primary or secondary).
  • Tabs.ScrollView: Optional scrollable wrapper for tab triggers. Enables horizontal scrolling when tabs overflow with automatic centering of active tab.
  • Tabs.Trigger: Interactive button for each tab. Handles press events to change active tab and measures its position for indicator animation.
  • Tabs.Label: Text content for tab triggers. Displays the tab title with appropriate styling.
  • Tabs.Indicator: Animated visual indicator for active tab. Smoothly transitions between tabs using spring or timing animations.
  • Tabs.Separator: Visual separator between tabs. Shows when the current tab value is not in the betweenValues array, with animated opacity transitions.
  • Tabs.Content: Container for tab panel content. Shows content when its value matches the active tab.

Usage

Basic Usage

The Tabs component uses compound parts to create navigable content sections.

tsx
<Tabs value="tab1" onValueChange={setActiveTab}>
  <Tabs.List>
    <Tabs.Indicator />
    <Tabs.Trigger value="tab1">
      <Tabs.Label>Tab 1</Tabs.Label>
    </Tabs.Trigger>
    <Tabs.Trigger value="tab2">
      <Tabs.Label>Tab 2</Tabs.Label>
    </Tabs.Trigger>
  </Tabs.List>
  <Tabs.Content value="tab1">...</Tabs.Content>
  <Tabs.Content value="tab2">...</Tabs.Content>
</Tabs>

Primary Variant

Default rounded primary style for tab triggers.

tsx
<Tabs value={activeTab} onValueChange={setActiveTab} variant="primary">
  <Tabs.List>
    <Tabs.Indicator />
    <Tabs.Trigger value="settings">
      <Tabs.Label>Settings</Tabs.Label>
    </Tabs.Trigger>
    <Tabs.Trigger value="profile">
      <Tabs.Label>Profile</Tabs.Label>
    </Tabs.Trigger>
  </Tabs.List>
  <Tabs.Content value="settings">...</Tabs.Content>
  <Tabs.Content value="profile">...</Tabs.Content>
</Tabs>

Secondary Variant

Underline style indicator for a more minimal appearance.

tsx
<Tabs value={activeTab} onValueChange={setActiveTab} variant="secondary">
  <Tabs.List>
    <Tabs.Indicator />
    <Tabs.Trigger value="overview">
      <Tabs.Label>Overview</Tabs.Label>
    </Tabs.Trigger>
    <Tabs.Trigger value="analytics">
      <Tabs.Label>Analytics</Tabs.Label>
    </Tabs.Trigger>
  </Tabs.List>
  <Tabs.Content value="overview">...</Tabs.Content>
  <Tabs.Content value="analytics">...</Tabs.Content>
</Tabs>

Scrollable Tabs

Handle many tabs with horizontal scrolling.

tsx
<Tabs value={activeTab} onValueChange={setActiveTab}>
  <Tabs.List>
    <Tabs.ScrollView scrollAlign="center">
      <Tabs.Indicator />
      <Tabs.Trigger value="tab1">
        <Tabs.Label>First Tab</Tabs.Label>
      </Tabs.Trigger>
      <Tabs.Trigger value="tab2">
        <Tabs.Label>Second Tab</Tabs.Label>
      </Tabs.Trigger>
      <Tabs.Trigger value="tab3">
        <Tabs.Label>Third Tab</Tabs.Label>
      </Tabs.Trigger>
      <Tabs.Trigger value="tab4">
        <Tabs.Label>Fourth Tab</Tabs.Label>
      </Tabs.Trigger>
      <Tabs.Trigger value="tab5">
        <Tabs.Label>Fifth Tab</Tabs.Label>
      </Tabs.Trigger>
    </Tabs.ScrollView>
  </Tabs.List>
  <Tabs.Content value="tab1">...</Tabs.Content>
  <Tabs.Content value="tab2">...</Tabs.Content>
  <Tabs.Content value="tab3">...</Tabs.Content>
  <Tabs.Content value="tab4">...</Tabs.Content>
  <Tabs.Content value="tab5">...</Tabs.Content>
</Tabs>

Disabled Tabs

Disable specific tabs to prevent interaction.

tsx
<Tabs value={activeTab} onValueChange={setActiveTab}>
  <Tabs.List>
    <Tabs.Indicator />
    <Tabs.Trigger value="active">
      <Tabs.Label>Active</Tabs.Label>
    </Tabs.Trigger>
    <Tabs.Trigger value="disabled" isDisabled>
      <Tabs.Label>Disabled</Tabs.Label>
    </Tabs.Trigger>
    <Tabs.Trigger value="another">
      <Tabs.Label>Another</Tabs.Label>
    </Tabs.Trigger>
  </Tabs.List>
  <Tabs.Content value="active">...</Tabs.Content>
  <Tabs.Content value="another">...</Tabs.Content>
</Tabs>

With Icons

Combine icons with labels for enhanced visual context.

tsx
<Tabs value={activeTab} onValueChange={setActiveTab}>
  <Tabs.List>
    <Tabs.Indicator />
    <Tabs.Trigger value="home">
      <Icon name="home" size={16} />
      <Tabs.Label>Home</Tabs.Label>
    </Tabs.Trigger>
    <Tabs.Trigger value="search">
      <Icon name="search" size={16} />
      <Tabs.Label>Search</Tabs.Label>
    </Tabs.Trigger>
  </Tabs.List>
  <Tabs.Content value="home">...</Tabs.Content>
  <Tabs.Content value="search">...</Tabs.Content>
</Tabs>

With Render Function

Use a render function on Tabs.Trigger to access state and customize content based on selection.

tsx
<Tabs value={activeTab} onValueChange={setActiveTab}>
  <Tabs.List>
    <Tabs.Indicator />
    <Tabs.Trigger value="settings">
      {({ isSelected, value, isDisabled }) => (
        <Tabs.Label
          className={isSelected ? 'text-accent font-medium' : 'text-foreground'}
        >
          Settings
        </Tabs.Label>
      )}
    </Tabs.Trigger>
    <Tabs.Trigger value="profile">
      {({ isSelected }) => (
        <>
          <Icon name="user" size={16} />
          <Tabs.Label className={isSelected ? 'text-accent' : 'text-muted'}>
            Profile
          </Tabs.Label>
        </>
      )}
    </Tabs.Trigger>
  </Tabs.List>
  <Tabs.Content value="settings">...</Tabs.Content>
  <Tabs.Content value="profile">...</Tabs.Content>
</Tabs>

With Separators

Add visual separators between tabs that show when the active tab is not between specified values.

tsx
<Tabs value={activeTab} onValueChange={setActiveTab}>
  <Tabs.List>
    <Tabs.ScrollView>
      <Tabs.Indicator />
      <Tabs.Trigger value="general">
        <Tabs.Label>General</Tabs.Label>
      </Tabs.Trigger>
      <Tabs.Separator betweenValues={['general', 'notifications']} />
      <Tabs.Trigger value="notifications">
        <Tabs.Label>Notifications</Tabs.Label>
      </Tabs.Trigger>
      <Tabs.Separator betweenValues={['notifications', 'profile']} />
      <Tabs.Trigger value="profile">
        <Tabs.Label>Profile</Tabs.Label>
      </Tabs.Trigger>
    </Tabs.ScrollView>
  </Tabs.List>
  <Tabs.Content value="general">...</Tabs.Content>
  <Tabs.Content value="notifications">...</Tabs.Content>
  <Tabs.Content value="profile">...</Tabs.Content>
</Tabs>

Example

tsx
import {
  Button,
  Checkbox,
  Description,
  ControlField,
  Label,
  Tabs,
  TextField,
} from 'heroui-native';
import { useState } from 'react';
import { View, Text } from 'react-native';
import Animated, {
  FadeIn,
  FadeOut,
  LinearTransition,
} from 'react-native-reanimated';

const AnimatedContentContainer = ({
  children,
}: {
  children: React.ReactNode;
}) => (
  <Animated.View
    entering={FadeIn.duration(200)}
    exiting={FadeOut.duration(200)}
    className="gap-6"
  >
    {children}
  </Animated.View>
);

export default function TabsExample() {
  const [activeTab, setActiveTab] = useState('general');

  const [showSidebar, setShowSidebar] = useState(true);
  const [accountActivity, setAccountActivity] = useState(true);
  const [name, setName] = useState('');

  return (
    <Tabs value={activeTab} onValueChange={setActiveTab} variant="primary">
      <Tabs.List>
        <Tabs.ScrollView>
          <Tabs.Indicator />
          <Tabs.Trigger value="general">
            <Tabs.Label>General</Tabs.Label>
          </Tabs.Trigger>
          <Tabs.Trigger value="notifications">
            <Tabs.Label>Notifications</Tabs.Label>
          </Tabs.Trigger>
          <Tabs.Trigger value="profile">
            <Tabs.Label>Profile</Tabs.Label>
          </Tabs.Trigger>
        </Tabs.ScrollView>
      </Tabs.List>

      <Animated.View
        layout={LinearTransition.duration(200)}
        className="px-4 py-6 border border-border rounded-xl"
      >
        <Tabs.Content value="general">
          <AnimatedContentContainer>
            <ControlField
              isSelected={showSidebar}
              onSelectedChange={setShowSidebar}
            >
              <ControlField.Indicator variant="checkbox" />
              <View className="flex-1">
                <Label>Show sidebar</Label>
                <Description>Display the sidebar navigation panel</Description>
              </View>
            </ControlField>
          </AnimatedContentContainer>
        </Tabs.Content>

        <Tabs.Content value="notifications">
          <AnimatedContentContainer>
            <ControlField
              isSelected={accountActivity}
              onSelectedChange={setAccountActivity}
            >
              <ControlField.Indicator variant="checkbox" />
              <View className="flex-1">
                <Label>Account activity</Label>
                <Description>
                  Notifications about your account activity
                </Description>
              </View>
            </ControlField>
          </AnimatedContentContainer>
        </Tabs.Content>

        <Tabs.Content value="profile">
          <AnimatedContentContainer>
            <TextField isRequired>
              <Label>Name</Label>
              <Input
                value={name}
                onChangeText={setName}
                placeholder="Enter your full name"
              />
            </TextField>
            <Button size="sm" className="self-start">
              <Button.Label>Update profile</Button.Label>
            </Button>
          </AnimatedContentContainer>
        </Tabs.Content>
      </Animated.View>
    </Tabs>
  );
}

You can find more examples in the GitHub repository.

API Reference

Tabs

proptypedefaultdescription
childrenReact.ReactNode-Children elements to be rendered inside tabs
valuestring-Currently active tab value
variant'primary' | 'secondary''primary'Visual variant of the tabs
classNamestring-Additional CSS classes for the container
animation"disable-all" | undefinedundefinedAnimation configuration. Use "disable-all" to disable all animations including children
onValueChange(value: string) => void-Callback when the active tab changes
...ViewPropsViewProps-All standard React Native View props are supported

Tabs.List

proptypedefaultdescription
childrenReact.ReactNode-Children elements to be rendered inside the list
classNamestring-Additional CSS classes
...ViewPropsViewProps-All standard React Native View props are supported

Tabs.ScrollView

proptypedefaultdescription
childrenReact.ReactNode-Children elements to be rendered inside the scroll view
scrollAlign'start' | 'center' | 'end' | 'none''center'Scroll alignment variant for the selected item
classNamestring-Additional CSS classes for the scroll view
contentContainerClassNamestring-Additional CSS classes for the content container
...ScrollViewPropsScrollViewProps-All standard React Native ScrollView props are supported

Tabs.Trigger

proptypedefaultdescription
childrenReact.ReactNode | ((props: TabsTriggerRenderProps) => React.ReactNode)-Children elements to be rendered inside the trigger, or a render function
valuestring-The unique value identifying this tab
isDisabledbooleanfalseWhether the trigger is disabled
classNamestring-Additional CSS classes
...PressablePropsPressableProps-All standard React Native Pressable props are supported

TabsTriggerRenderProps

When using a render function for children, the following props are provided:

propertytypedescription
isSelectedbooleanWhether this trigger is currently selected
valuestringThe value of the trigger
isDisabledbooleanWhether the trigger is disabled

Tabs.Label

proptypedefaultdescription
childrenReact.ReactNode-Text content to be rendered as label
classNamestring-Additional CSS classes
...TextPropsTextProps-All standard React Native Text props are supported

Tabs.Indicator

proptypedefaultdescription
childrenReact.ReactNode-Custom indicator content
classNamestring-Additional CSS classes
animationTabsIndicatorAnimation-Animation configuration
isAnimatedStyleActivebooleantrueWhether animated styles (react-native-reanimated) are active
...Animated.ViewPropsAnimated.ViewProps-All Reanimated Animated.View props are supported

TabsIndicatorAnimation

Animation configuration for Tabs.Indicator component. Can be:

  • false or "disabled": Disable all animations
  • true or undefined: Use default animations
  • object: Custom animation configuration
proptypedefaultdescription
state'disabled' | boolean-Disable animations while customizing properties
width.type'spring' | 'timing''spring'Type of animation to use
width.configWithSpringConfig | WithTimingConfig{ stiffness: 1200, damping: 120 } (spring) or { duration: 200 } (timing)Reanimated animation configuration
height.type'spring' | 'timing''spring'Type of animation to use
height.configWithSpringConfig | WithTimingConfig{ stiffness: 1200, damping: 120 } (spring) or { duration: 200 } (timing)Reanimated animation configuration
translateX.type'spring' | 'timing''spring'Type of animation to use
translateX.configWithSpringConfig | WithTimingConfig{ stiffness: 1200, damping: 120 } (spring) or { duration: 200 } (timing)Reanimated animation configuration

Tabs.Separator

proptypedefaultdescription
betweenValuesstring[]-Array of tab values between which the separator should be visible. The separator shows when the current tab value is NOT in this array
isAlwaysVisiblebooleanfalseIf true, opacity is always 1 regardless of the current tab value
classNamestring-Additional CSS classes
animationTabsSeparatorAnimation-Animation configuration
isAnimatedStyleActivebooleantrueWhether animated styles (react-native-reanimated) are active
childrenReact.ReactNode-Custom separator content
...Animated.ViewPropsAnimated.ViewProps-All Reanimated Animated.View props are supported

Note: The following style properties are occupied by animations and cannot be set via className:

  • opacity - Animated for separator visibility transitions (0 when current tab is in betweenValues, 1 when not)

To customize these properties, use the animation prop. To completely disable animated styles and use your own via className or style prop, set isAnimatedStyleActive={false}.

TabsSeparatorAnimation

Animation configuration for Tabs.Separator component. Can be:

  • false or "disabled": Disable all animations
  • true or undefined: Use default animations
  • object: Custom animation configuration
proptypedefaultdescription
state'disabled' | boolean-Disable animations while customizing properties
opacity.value[number, number][0, 1]Opacity values [hidden, visible]
opacity.timingConfigWithTimingConfig{ duration: 200 }Animation timing configuration

Tabs.Content

proptypedefaultdescription
childrenReact.ReactNode-Children elements to be rendered inside the content
valuestring-The value of the tab this content belongs to
classNamestring-Additional CSS classes
...ViewPropsViewProps-All standard React Native View props are supported

Hooks

useTabs

Hook to access tabs root context values within custom components or compound components.

tsx
import { useTabs } from 'heroui-native';

const CustomComponent = () => {
  const { value, onValueChange, nativeID } = useTabs();
  // ... your implementation
};

Returns: UseTabsReturn

propertytypedescription
valuestringCurrently active tab value
onValueChange(value: string) => voidCallback function to change the active tab
nativeIDstringUnique identifier for the tabs instance

Note: This hook must be used within a Tabs component. It will throw an error if called outside of the tabs context.

useTabsMeasurements

Hook to access tab measurements context values for managing tab trigger positions and dimensions.

tsx
import { useTabsMeasurements } from 'heroui-native';

const CustomIndicator = () => {
  const { measurements, variant } = useTabsMeasurements();
  // ... your implementation
};

Returns: UseTabsMeasurementsReturn

propertytypedescription
measurementsRecord<string, ItemMeasurements>Record of measurements for each tab trigger
setMeasurements(key: string, measurements: ItemMeasurements) => voidFunction to update measurements for a tab trigger
variant'primary' | 'secondary'Visual variant of the tabs

ItemMeasurements

propertytypedescription
widthnumberWidth of the tab trigger in pixels
heightnumberHeight of the tab trigger in pixels
xnumberX position of the tab trigger

Note: This hook must be used within a Tabs component. It will throw an error if called outside of the tabs context.

useTabsTrigger

Hook to access tab trigger context values within custom components or compound components.

tsx
import { useTabsTrigger } from 'heroui-native';

const CustomLabel = () => {
  const { value, isSelected, nativeID } = useTabsTrigger();
  // ... your implementation
};

Returns: UseTabsTriggerReturn

propertytypedescription
valuestringThe value of this trigger
nativeIDstringUnique identifier for this trigger
isSelectedbooleanWhether this trigger is currently selected

Note: This hook must be used within a Tabs.Trigger component. It will throw an error if called outside of the trigger context.