Bottom Nav

The mobile tab bar — three to five destinations, pinned to the bottom where thumbs are, with the home-indicator safe area accounted for.

src/components/ui/bottom-nav.tsx

About

From the component's own source, where it was written.

The mobile tab bar: three to five destinations, pinned to the bottom.

**Bottom, because that is where thumbs are.** On a phone held one-handed the top of the screen is the hardest region to reach and the bottom third is the easiest — which is why every mobile OS puts primary navigation there and why a top nav bar on mobile costs a reach or a second hand.

**It respects the home indicator.** env(safe-area-inset-bottom) is added to the padding, so on a notched phone the bar sits above the gesture area instead of underneath it, where the last few pixels of every tap get eaten by the system. This is the single most common defect in hand-rolled tab bars.

**Destinations, not actions.** These switch *where you are*; a tab bar with "Share" in it is a toolbar wearing the wrong clothes. Keep it to five — past that the targets fall below the ~44px comfortable minimum and labels truncate to nonsense.

It renders <nav> + a list of links, with aria-current="page" on the active one — the same markup as any other navigation, so it is announced as navigation and works with the browser's own history.

Install

npx astralyx-ui add bottom-nav

Usage

import { BottomNav } from '@/components/ui/bottom-nav'

<BottomNav items={items} value={tab} onChange={setTab} />

Composer

Change the props and the source below follows.

home
Props
tsx
<BottomNav
  items={items}
  value={tab}
  onChange={setTab}
  showLabels={true}
/>

Examples

Five destinations, with badges

API

Generated from the source, so it cannot fall behind the code. One table per exported component, then hooks, functions and types.

BottomNav

The mobile tab bar: three to five destinations, pinned to the bottom.

Extends Omit<ComponentProps<'nav'>, 'onChange'>.

PropTypeDefaultDescription
itemsBottomNavItem[]required{ value, label, icon, href?, badge?, disabled? }.
valuestring——
onChange(value: string, item: BottomNavItem) => void——
showLabelsbooleantrueHide the text, leaving icons only. Costs clarity; buys room.
fixedbooleanfalsePosition it fixed to the viewport bottom, as it would be in an app.
labelstring'Primary'—

Types

BottomNavItem

FieldTypeDescription
valuestring—
labelReactNode—
iconReactNode—
href?string—
badge?number | booleanA count, or true for a plain dot.
disabled?boolean—

Behaviour

TopicDetailDescription
bottomwhere thumbs areHeld one-handed, the top of a phone is the hardest region to reach. A top nav bar on mobile costs a reach or a second hand.
safe areaenv(safe-area-inset-bottom)Added to the padding, so the bar clears the home indicator. Without it the last row of pixels is not tappable — the most common defect in hand-rolled tab bars.
destinations, not actionskeep it to fiveA tab bar with "Share" in it is a toolbar wearing the wrong clothes. Past five, targets fall below ~44px and labels truncate to nonsense.
aria-currentpageThe navigation equivalent of aria-selected. Signalling the current page with colour alone says nothing to a screen reader.