A bottom bar whose highlight glides to the selected item, and that can fold to it or slide away while the page scrolls down.
Today
- SSpotify08:12€10.99
- FFigma09:30€15.00
- UUber12:42€6.05
- NNotion14:05€8.00
- AAirbnb17:03€256.73
- VVercel18:21€20.00
- AApple19:18€2.99
- DDribbble20:47€52.92
- NNetflix21:15€13.49
- LLinear22:02€8.00
Installation
Usage
import {
BottomNavigation,
BottomNavigationAccessory,
BottomNavigationHighlight,
BottomNavigationHighlightItem,
BottomNavigationItem,
BottomNavigationLabel,
} from '@animate-ui/react/primitives/bottom-navigation';
const ITEMS = [
{ value: 'home', href: '/', label: 'Home', icon: <HomeIcon /> },
{ value: 'search', href: '/search', label: 'Search', icon: <SearchIcon /> },
];
export function AppNavigation() {
return (
<BottomNavigation
defaultValue="home"
scrollBehavior="minimize"
aria-label="Main"
className="fixed inset-x-0 bottom-4 mx-auto flex w-fit items-center gap-2"
>
<BottomNavigationHighlight className="absolute inset-0 rounded-full bg-muted">
<ul className="flex rounded-full border bg-background p-1.5">
{ITEMS.map((item) => (
<li key={item.value}>
<BottomNavigationHighlightItem value={item.value}>
<BottomNavigationItem
value={item.value}
href={item.href}
className="flex items-center px-4 py-2"
>
{item.icon}
<BottomNavigationLabel className="pl-2">
{item.label}
</BottomNavigationLabel>
</BottomNavigationItem>
</BottomNavigationHighlightItem>
</li>
))}
</ul>
</BottomNavigationHighlight>
<BottomNavigationAccessory className="rounded-full border bg-background p-1.5">
<button className="size-11 rounded-full hover:bg-muted">+</button>
</BottomNavigationAccessory>
</BottomNavigation>
);
}Items are links: give them an href, or asChild with your router's link or a button. The selected item gets aria-current="page" and data-state="active". With a router, pass the current route as value so the highlight follows navigation. BottomNavigationHighlight and BottomNavigationHighlightItem are optional; without them, style the active item with data-state="active".
scrollBehavior sets what the bar does while the page scrolls down: "minimize" folds it to the selected item, like iOS' tab bar, "hide" slides it out, and "none" (the default) leaves it. It comes back as soon as the page scrolls up or reaches the top, an item takes focus, or the bar is tapped. It listens to the window, or to scrollContainer when the page scrolls inside an element. To fold, items shrink to nothing, padding included: space them with padding rather than a gap, which would stay.
BottomNavigationLabel and BottomNavigationAccessory are optional:
- A label shows only on the selected item, so the bar stays icons and one name. Its text stays in the accessibility tree, so it names the item even while closed.
- The accessory (a button, a player, a search field) stays beside the bar, folded or not: it follows the bar as it folds. Pressing or focusing it leaves the bar folded.
A closed label takes no room: put its spacing inside it (pl-2), not on the item.
Animation
- The highlight slides to the selected item on a spring (stiffness 300, damping 30): one surface is handed from the previous item to the new one, at full opacity (Highlight).
- Items scale to 0.97 while pressed, on the buttons' spring.
- Labels open on the highlight's spring, so the highlight widens with the selected item: the new label grows from nothing out of a 4px blur as the previous one narrows and blurs away.
- Both scroll behaviors start once the page has scrolled down by 6px and run on a critically damped spring (stiffness 300, damping 35).
"minimize": the other items narrow to nothing as they fade out into a 4px blur, the bar closing around the selected one with its highlight; they come back the same way."hide": the bar slides out past the bottom edge, and back on the way up.
<AnimateUIProvider tempo>paces every spring.- Under reduced motion, the highlight moves at once, items don't scale, labels change width at once (fading), the bar folds at once, and the hidden bar fades out instead of sliding (and can’t be clicked while hidden).
API Reference
BottomNavigation
| Prop | Type | Default |
|---|---|---|
defaultValue? | string | - |
value? | string | - |
onValueChange? | (value: string) => void | - |
scrollBehavior? | 'none' | 'hide' | 'minimize' | 'none' |
scrollContainer? | React.RefObject<HTMLElement | null> | - |
transition? | Transition | { type: 'spring', stiffness: 300, damping: 35, restDelta: 0.01 } |
...props? | HTMLMotionProps<'nav'> | - |
BottomNavigationHighlight
Takes the Highlight props, except controlledItems and value.
| Prop | Type | Default |
|---|---|---|
transition? | Transition | { type: 'spring', stiffness: 300, damping: 30 } |
BottomNavigationHighlightItem
Takes the HighlightItem props.
| Prop | Type | Default |
|---|---|---|
value | string | - |
BottomNavigationItem
| Prop | Type | Default |
|---|---|---|
asChild? | boolean | false |
value | string | - |
tapScale? | number | 0.97 |
...props? | HTMLMotionProps<'a'> | - |
BottomNavigationLabel
Goes inside a BottomNavigationItem.
| Prop | Type | Default |
|---|---|---|
transition? | Transition | { type: 'spring', stiffness: 300, damping: 30 } |
...props? | HTMLMotionProps<'span'> | - |
BottomNavigationAccessory
| Prop | Type | Default |
|---|---|---|
...props? | HTMLMotionProps<'div'> | - |
useBottomNavigation
Returns activeValue, handleValueChange, scrollBehavior and minimized.