Overview
Mobile Tab Bar is the bottom navigation of a mobile app. Three to five destinations sit in an evenly divided bar; a shared-layout indicator — a soft pill behind the icon, a solid blob or a top line — slides to the active tab on a spring, and the icon gives a small bounce when selected. Tabs can carry badge counts, labels can be always visible, active-only or hidden, an optional raised primary action sits in the middle, and the bar pads itself for the home indicator with env(safe-area-inset-bottom). Semantically it is one thing: a navigation landmark whose tabs are links (with href) or buttons, and the active one is marked with aria-current="page" — whether you route between pages or swap views in state.
01Installation
Method 01 · Registry
CLI verifiedUses the shadcn CLI in a project initialised with shadcn init. Installs the file to components/ui/mobile-tab-bar.tsx and adds its dependencies.
npx shadcn@latest add https://wancraft.my/r/mobile-tab-bar.jsonMethod 02 · Copy and paste
- 1. Install the dependencies below.
- 2. Copy the source from the Code tab into
components/ui/mobile-tab-bar.tsx. - 3. Import it and adjust props.
npm install motion- Dependencies
- motion
- Peer
- react ≥ 18, tailwindcss ≥ 4
- Added
- 13 Oct 2026
- Updated
- —
02Usage
The default configuration. Change properties above to generate a tailored example in the Usage tab.
import { MobileTabBar, type MobileTabItem } from "@/components/ui/mobile-tab-bar";
import { Bell, Compass, Home, Plus, User } from "lucide-react";
export function MobileTabBarExample() {
const tabs: MobileTabItem[] = [
{ id: "home", label: "Home", icon: <Home />, href: "/" },
{ id: "explore", label: "Explore", icon: <Compass />, href: "/explore" },
{ id: "inbox", label: "Inbox", icon: <Bell />, href: "/inbox", badge: 3 },
{ id: "profile", label: "Profile", icon: <User />, href: "/profile" },
];
return (
<MobileTabBar
items={tabs}
defaultValue="home"
primaryAction={{ label: "New post", icon: <Plus /> }}
fixed
/>
);
}03Props
| Prop | Type | Default | Description |
|---|---|---|---|
| items | MobileTabItem[] | — | Required. 3–5 tabs: { id, label, icon, activeIcon?, badge?, href? }. With href a tab renders a link, otherwise a button. |
| value | string | — | Active tab id (controlled). |
| defaultValue | string | — | Initially active tab id (uncontrolled). Defaults to the first item. |
| onValueChange | (id: string) => void | — | Called when a different tab is pressed. |
| indicator | "pill" | "blob" | "line" | "pill" | Shared-layout indicator style. |
| labels | "always" | "active" | "never" | "always" | Visible labels; hidden labels still name the tab for screen readers. |
| primaryAction | { label: string; icon: ReactNode; onClick? } | — | Raised centre button (an action, not a destination). |
| bounce | boolean | true | Bounce the icon when its tab becomes active. |
| spring | { stiffness; damping; mass } | { 500, 36, 0.8 } | Spring for the sliding indicator. |
| accent | string | "#FF7448" | Indicator, primary action and focus ring colour. |
| safeArea | boolean | true | Bottom padding of env(safe-area-inset-bottom) (needs viewport-fit=cover). |
| fixed | boolean | false | Pin the bar to the bottom of the viewport. |
| label | string | "Main" | Accessible name of the nav landmark. |
| ...props | HTMLAttributes<HTMLElement> | — | Forwarded to the <nav>. |
04Features
- 01Sliding indicator via shared layout (layoutId): soft pill, solid blob or top line
- 02Icon bounce on selection and spring-popped badge counts (99+)
- 03Optional raised centre primary action, ringed to sit above the bar
- 04Labels always, active-only or hidden
- 05Controlled or uncontrolled; links (href) for routing or buttons for state
- 06Safe-area padding for home-indicator devices; optional fixed positioning
05Accessibility
- One clear model: a <nav> landmark with a list of links or buttons; the active tab has aria-current="page". It is intentionally not a tablist, because a bottom bar switches app destinations rather than panels in place.
- Every tab is named by its label, even when labels are visually hidden; badge counts are announced (“Inbox, 3 new”).
- The primary action is a separate labelled button, not a destination.
- Icons and the indicator are aria-hidden; visible focus rings on every control.
- With prefers-reduced-motion the indicator moves instantly and icons do not bounce.
06Limitations
- Designed for 3–5 tabs; more will crowd the bar on small phones.