TabContainer
Tab container with top or bottom tab bar for switching between content panels.
Basic Usage
import { TabContainer } from '@multinaire/expo-ui';
<TabContainer
position="top"
itemsMap={{
posts: <PostsTab />,
likes: <LikesTab />,
saved: <SavedTab />,
}}
selectedItem="posts"
onChange={(item) => setSelectedItem(item)}
/>
Props
| Prop | Type | Default | Description |
|---|---|---|---|
position | 'top' | 'bottom' | — | Position of the tab bar relative to the content |
itemsMap | Record<T, ReactElement> | — | Map of tab key to the React element rendered for that tab |
selectedItem | T | — | Currently selected tab key |
flex | number | — | Flex value applied to the container, the page wrapper, and the page itself. Omitted, all three take flexGrow: 1 |
scrollEnabled | boolean | true | Enable scrolling when there are more tabs than fit the screen |
topTabBehavior | 'scroll' | 'fill' | 'scroll' | Controls whether top tab headers scroll or fill |
onChange | (item: T) => void | — | Callback invoked when the user switches tabs |
style | ContainerStyleProps | — | Additional styles applied to the page content wrapper |
tabStyle | ContainerStyleProps | — | Additional styles applied to the tab bar container |
buttonStyle | TabButtonStyle | — | Active/inactive colors for all tab buttons |
Examples
Top Tabs
const [tab, setTab] = useState('all');
<TabContainer
position="top"
itemsMap={{
all: <AllItems />,
active: <ActiveItems />,
completed: <CompletedItems />,
}}
selectedItem={tab}
onChange={setTab}
/>
Bottom Tabs
<TabContainer
position="bottom"
itemsMap={{
home: <HomeScreen />,
search: <SearchScreen />,
profile: <ProfileScreen />,
}}
selectedItem={activeTab}
onChange={setActiveTab}
/>
Fill Behavior
<TabContainer
position="top"
topTabBehavior="fill"
itemsMap={{
tab1: <Tab1 />,
tab2: <Tab2 />,
}}
selectedItem={selectedTab}
onChange={setSelectedTab}
/>
Profile Tabs
<Container flex={1}>
<ProfileHeader user={user} />
<TabContainer
position="top"
topTabBehavior="fill"
itemsMap={{
posts: <UserPosts userId={user.id} />,
media: <UserMedia userId={user.id} />,
likes: <UserLikes userId={user.id} />,
}}
selectedItem={profileTab}
onChange={setProfileTab}
/>
</Container>
Inside a ScrollContainer
Nothing to configure — the tab view grows from its content's own size, so it
fills a parent of fixed height and sizes to the tabs inside a ScrollContainer.
Panels do have to carry their own height there: the wrapper is content-sized, so
a flex={1} panel inside it measures zero.
<ScrollContainer flex={1} padding={variables.padding.medium}>
<ProfileHeader user={user} />
<TabContainer
position="top"
topTabBehavior="fill"
itemsMap={{
posts: <Container height={400} />,
media: <Container height={400} />,
}}
selectedItem={profileTab}
onChange={setProfileTab}
/>
</ScrollContainer>
Pass flex only to override that with real flex semantics — flex={1} to
force the tab view to take a share of a fixed-height parent regardless of its
content, or flex={0} to stop it growing into leftover space.
Disable Swipe
<TabContainer
scrollEnabled={false}
position="top"
itemsMap={tabs}
selectedItem={currentTab}
onChange={setCurrentTab}
/>