Skip to main content

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​

PropTypeDefaultDescription
position'top' | 'bottom'—Position of the tab bar relative to the content
itemsMapRecord<T, ReactElement>—Map of tab key to the React element rendered for that tab
selectedItemT—Currently selected tab key
flexnumber—Flex value applied to the container, the page wrapper, and the page itself. Omitted, all three take flexGrow: 1
scrollEnabledbooleantrueEnable 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
styleContainerStyleProps—Additional styles applied to the page content wrapper
tabStyleContainerStyleProps—Additional styles applied to the tab bar container
buttonStyleTabButtonStyle—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}
/>