A phone's bottom navigation: the picked tab opens to its label while its highlight stretches across like a drop of liquid.
Installation
When to use
- The main navigation of a phone app or a mobile web app: four or five places people move between all day.
- When the bar should feel alive without getting in the way: the picked tab names itself, the others stay icons.
When not to use
- Navigation on a desktop screen: a sidebar or top tabs fit better.
- More than five places, or labels people need to read before they pick: use Tabs or a menu.
Usage
Tab Bar with your own data: a whole component you can paste as a page.
example.tsx
"use client"
import { TabBar } from "@/components/dashral/pro/tab-bar"
import { useState } from "react"
import { Home, Inbox, Search, UserRound } from "lucide-react"
const tabs = [
{ value: "home", label: "Home", icon: <Home /> },
{ value: "search", label: "Search", icon: <Search /> },
{ value: "inbox", label: "Inbox", icon: <Inbox />, badge: 3 },
{ value: "profile", label: "Profile", icon: <UserRound /> },
]
export default function App() {
const [tab, setTab] = useState("home")
return (
<div className="fixed inset-x-0 bottom-4 flex justify-center">
<TabBar label="App" tabs={tabs} value={tab} onValueChange={setTab} />
</div>
)
}API reference
TabBar
A bottom tab bar for a phone app: the tabs not picked show their icon, the picked one opens to its label, and the highlight stretches between them like a drop of liquid. Controlled or not; arrow keys move between tabs.
| Prop | Type | Default |
|---|---|---|
tabs | TabBarItem[]Required. | — |
value | string | — |
defaultValue | string | — |
onValueChange | (value: string) => void | — |
label | stringThe bar's name, for screen readers. | "Main" |
className | string | — |
classNames | TabBarClassNames | — |
type TabBarItem
| Prop | Type | Default |
|---|---|---|
value | string | — |
label | string | — |
icon | React.ReactNode | — |
badge | numberA count on its icon (unread, new); 0 or left out shows none. | — |
type TabBarClassNames
| Prop | Type | Default |
|---|---|---|
root | string | — |
tab | string | — |
indicator | string | — |
label | string | — |
Keyboard
| Keys | Action |
|---|---|
| Tab | Reach the selected tab |
| ←or→ | Pick the previous or next tab (it wraps) |
| HomeorEnd | Pick the first or last tab |
Accessibility
- A tablist named by label; each tab has role tab and aria-selected, and only the selected one is in the tab order.
- A tab with a badge is read with it ("Inbox, 3 new").
- The tabs not picked show only their icon; their names are still read.
Motion
- The highlight moves like a drop of liquid: its leading edge runs ahead, the trailing one follows, and it thins a little as it stretches.
- The picked tab opens to its label while the one left closes to its icon. Phones get a light buzz on a pick.
- Smooth tweens on [0.4, 0, 0.2, 1], never springs. With reduced motion the highlight and labels change without moving.