BottomTabBar
Bottom navigation with safe areas, active states, badges and an optional main action.
Installation
npx axiom add bottom-tab-barpnpm dlx axiom add bottom-tab-baryarn dlx axiom add bottom-tab-barbun x axiom add bottom-tab-barAlso copies: icon, badge, text, tappable and useControllableState. Installs react-native-safe-area-context.
Colors come from bottomTabBar.{fixed,floating} and bottomTabBar.item.{default,active,disabled} in the component tokens.
Usage
import { BottomTabBar } from "@/components/ui/bottom-tab-bar";
<BottomTabBar value={route} onValueChange={navigate}>
<BottomTabBar.Item value="home" icon="home" label="Home" />
<BottomTabBar.Item value="inbox" icon="inbox" label="Inbox" badge={3} />
<BottomTabBar.Item value="profile" icon="user" label="Profile" />
</BottomTabBar>;Props
BottomTabBar
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | — | Value of the active item. Keep it aligned with the current route name. |
defaultValue | string | — | Initial active item when the bar manages its own state. |
onValueChange | (value: string) => void | — | Called when a different enabled item is pressed. This is where a router adapter navigates. |
variant | 'fixed' | 'floating' | 'fixed' | Pins the bar to the screen edge or renders it as an inset pill. |
haptic | HapticKind | false | — | Haptic played when the user switches to another item. Off unless you pass a kind. |
safeArea | boolean | true | Adds the bottom safe-area inset. Disable it only when the parent already provides that space. |
children | ReactNode | — | Two to five BottomTabBar.Item elements, and a BottomTabBar.Action where you want a raised action. |
style | StyleProp<ViewStyle> | — | Extra styles for the outer bar. |
BottomTabBar.Item
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | — | Stable value used by the root to identify this destination. |
label | string | — | Short destination name displayed below the icon. |
icon | IconName | ReactNode | — | Icon shown when the item is inactive. |
activeIcon | IconName | ReactNode | icon | Optional filled or heavier icon for the active state. |
badge | number | string | boolean | — | Count, short text or dot. Counts above 99 display as 99+. |
disabled | boolean | false | Keeps the destination visible but blocks selection. |
accessibilityLabel | string | label | Spoken destination name. Badge counts are announced separately. |
testID | string | — | Identifier for navigation tests. |
BottomTabBar.Action
A View that centers a raised action, such as a compose button, among the items. It sits where you write it in the row and never becomes the selected tab. It forwards every View prop.
Hiding on the keyboard
The bar doesn’t watch the keyboard. When a screen has a field and wants the room, hide the bar there with useKeyboardVisible:
import { useKeyboardVisible } from "@/hooks/use-keyboard-visible";
const keyboardVisible = useKeyboardVisible();
{keyboardVisible ? null : (
<BottomTabBar value={route} onValueChange={navigate}>
{items}
</BottomTabBar>
)}The bar sets the tab accessibility role and selected state. It does not render navigation screens or own a history stack.
Haptics
The bar plays nothing by default. Some apps play a "selection" haptic when the user switches tab: pass it to BottomTabBar. Pressing the active item plays nothing. Nothing plays until your app connects a haptics library: see Haptics.
Use cases
Main app navigation
Use three to five stable destinations at the top level of the app. A badge can report new content without changing the route label.
<BottomTabBar value={route.name} onValueChange={router.replace}>
<BottomTabBar.Item value="home" icon="home" label="Home" />
<BottomTabBar.Item
value="inbox"
icon="inbox"
label="Inbox"
badge={unreadCount}
/>
<BottomTabBar.Item value="calendar" icon="calendar" label="Calendar" />
<BottomTabBar.Item value="profile" icon="user" label="Profile" />
</BottomTabBar>Navigation with a main action
The raised middle control is for a frequent creation action. It opens a flow but does not represent a destination.
<BottomTabBar value={route} onValueChange={navigate}>
<BottomTabBar.Item value="home" icon="home" label="Home" />
<BottomTabBar.Item value="search" icon="search" label="Search" />
<BottomTabBar.Action>
<IconButton
icon="plus"
variant="solid"
accessibilityLabel="Create playlist"
onPress={createPlaylist}
/>
</BottomTabBar.Action>
<BottomTabBar.Item value="saved" icon="heart" label="Saved" />
<BottomTabBar.Item value="profile" icon="user" label="Profile" />
</BottomTabBar>Floating navigation over immersive content
Use the floating variant when content reaches every screen edge, such as a map. Keep enough bottom padding so controls remain reachable above the bar.
<BottomTabBar variant="floating" value={route} onValueChange={navigate}>
<BottomTabBar.Item value="map" icon="map" label="Map" />
<BottomTabBar.Item value="explore" icon="search" label="Explore" />
<BottomTabBar.Item value="saved" icon="heart" label="Saved" />
<BottomTabBar.Item value="profile" icon="user" label="Profile" />
</BottomTabBar>Navigation libraries
The component stays presentational. A small adapter maps Expo Router or React Navigation routes to value and onValueChange.
Related
- Tab
- Badge
- Snackbar, which can sit above the bar
- Hide-on-scroll