# BottomNav

A floating capsule navigation bar below md, with 3 to 5 top-level destinations. The glass surface sits above the bottom safe area with a small gap. Labels are visible by default and can be visually hidden with showLabels. It is separate from Sidebar: an app that has both renders its own items in each. The page viewport needs viewport-fit=cover.

**Dependencies:** npm: 1 direct, 3 total; registry: 3 direct, 3 total.

## Usage

### When to use

- Show a short list of top-level destinations at the bottom of a small screen.

### When not to use

- Put actions such as Create or Share in the navigation. Use Button.
- Show the same bar next to a visible Sidebar.

## Examples

### Three destinations

```tsx
<div data-slot="bottom-nav-preview" className="mx-auto flex h-80 w-[393px] shrink-0 flex-col justify-between overflow-hidden rounded-container border border-border bg-background-surface">
  <div className="flex flex-col gap-xs p-lg">
    <p className="text-sm font-medium">Your workspace</p>
    <p className="text-xs text-text-secondary">Choose a destination below.</p>
  </div>
  <BottomNav label="Main" value={destination} className="relative md:block">
    <BottomNavItem href="#home" onClick={selectDestination} icon={<IconHome size={20} />} activeIcon={<IconHomeFilled size={20} />} label="Home" active />
    <BottomNavItem href="#search" onClick={selectDestination} icon={<IconSearch size={20} />} activeIcon={<IconSearchFilled size={20} />} label="Search" />
    <BottomNavItem href="#inbox" onClick={selectDestination} icon={<IconMail size={20} />} activeIcon={<IconMailFilled size={20} />} label="Inbox" count={3} />
  </BottomNav>
</div>
```

### Five destinations

```tsx
<div data-slot="bottom-nav-preview" className="mx-auto flex h-80 w-[393px] shrink-0 flex-col justify-between overflow-hidden rounded-container border border-border bg-background-surface">
  <div className="flex flex-col gap-xs p-lg">
    <p className="text-sm font-medium">Your workspace</p>
    <p className="text-xs text-text-secondary">Choose a destination below.</p>
  </div>
  <BottomNav label="Main" value={destination} className="relative md:block">
    <BottomNavItem href="#home" onClick={selectDestination} icon={<IconHome size={20} />} activeIcon={<IconHomeFilled size={20} />} label="Home" active />
    <BottomNavItem href="#search" onClick={selectDestination} icon={<IconSearch size={20} />} activeIcon={<IconSearchFilled size={20} />} label="Search" />
    <BottomNavItem href="#inbox" onClick={selectDestination} icon={<IconMail size={20} />} activeIcon={<IconMailFilled size={20} />} label="Inbox" count={3} />
    <BottomNavItem href="#projects" onClick={selectDestination} icon={<IconLayoutGrid size={20} />} activeIcon={<IconLayoutGridFilled size={20} />} label="Projects" />
    <BottomNavItem href="#profile" onClick={selectDestination} icon={<IconUser size={20} />} activeIcon={<IconUserFilled size={20} />} label="Profile" />
  </BottomNav>
</div>
```

### Current route

```tsx
<div data-slot="bottom-nav-preview" className="mx-auto flex h-80 w-[393px] shrink-0 flex-col justify-between overflow-hidden rounded-container border border-border bg-background-surface">
  <div className="flex flex-col gap-xs p-lg">
    <p className="text-sm font-medium">Your workspace</p>
    <p className="text-xs text-text-secondary">Choose a destination below.</p>
  </div>
  <BottomNav label="Main" value={destination} className="relative md:block">
    <BottomNavItem href="#home" onClick={selectDestination} icon={<IconHome size={20} />} activeIcon={<IconHomeFilled size={20} />} label="Home" active />
    <BottomNavItem href="#search" onClick={selectDestination} icon={<IconSearch size={20} />} activeIcon={<IconSearchFilled size={20} />} label="Search" />
    <BottomNavItem href="#inbox" onClick={selectDestination} icon={<IconMail size={20} />} activeIcon={<IconMailFilled size={20} />} label="Inbox" count={3} />
    <BottomNavItem href="#projects" onClick={selectDestination} icon={<IconLayoutGrid size={20} />} activeIcon={<IconLayoutGridFilled size={20} />} label="Projects" />
  </BottomNav>
</div>
```

### Custom link elements

```tsx
<div data-slot="bottom-nav-preview" className="mx-auto flex h-80 w-[393px] shrink-0 flex-col justify-between overflow-hidden rounded-container border border-border bg-background-surface">
  <div className="flex flex-col gap-xs p-lg">
    <p className="text-sm font-medium">Your workspace</p>
    <p className="text-xs text-text-secondary">Choose a destination below.</p>
  </div>
  <BottomNav label="Main" value={destination} className="relative md:block">
    <BottomNavItem href="#home" onClick={selectDestination} icon={<IconHome size={20} />} activeIcon={<IconHomeFilled size={20} />} label="Home" active asChild>
      <a href="#home">Home</a>
    </BottomNavItem>
    <BottomNavItem href="#search" onClick={selectDestination} icon={<IconSearch size={20} />} activeIcon={<IconSearchFilled size={20} />} label="Search" asChild>
      <a href="#search">Search</a>
    </BottomNavItem>
    <BottomNavItem href="#inbox" onClick={selectDestination} icon={<IconMail size={20} />} activeIcon={<IconMailFilled size={20} />} label="Inbox" asChild>
      <a href="#inbox">Inbox</a>
    </BottomNavItem>
  </BottomNav>
</div>
```

### Icons only

Hide visible labels while preserving destination names for assistive technology.

```tsx
<div data-slot="bottom-nav-preview" className="mx-auto flex h-80 w-[393px] shrink-0 flex-col justify-between overflow-hidden rounded-container border border-border bg-background-surface">
  <div className="flex flex-col gap-xs p-lg">
    <p className="text-sm font-medium">Your workspace</p>
    <p className="text-xs text-text-secondary">Choose a destination below.</p>
  </div>
  <BottomNav label="Main" value={destination} showLabels={false} className="relative md:block">
    <BottomNavItem href="#home" onClick={selectDestination} icon={<IconHome size={20} />} activeIcon={<IconHomeFilled size={20} />} label="Home" active />
    <BottomNavItem href="#search" onClick={selectDestination} icon={<IconSearch size={20} />} activeIcon={<IconSearchFilled size={20} />} label="Search" />
    <BottomNavItem href="#inbox" onClick={selectDestination} icon={<IconMail size={20} />} activeIcon={<IconMailFilled size={20} />} label="Inbox" count={3} />
  </BottomNav>
</div>
```

## Do and don't

### Do

- Use 3 to 5 destinations with short labels.
- Pad page content so the bar never covers the last row.
- In these embedded demos, relative md:block keeps the bar inside its cell and visible on desktop. Omit those demo classes for a fixed mobile bar in an application.
- Use the current route as value, or set active on one item. The component does not own navigation state.
- Keep labels visible for unfamiliar destinations. Set showLabels={false} only when the icons are familiar; always supply each item label.
- Leave room below page content for the capsule height, its bottom gap and the safe-area inset.
- Supply a filled activeIcon for every destination. Each embedded example uses independent local selection and a 393px mobile canvas; scroll the canvas horizontally when the documentation cell is narrower.

### Don't

- Do not put actions (Create, Share) in the bar; use a Button.
- Do not show it next to an open Sidebar.

## Props

| Name | Type | Values | Default | Description |
| --- | --- | --- | --- | --- |
| `label` | `string` |  | `Main` | On BottomNav: accessible name of the navigation landmark. On BottomNavItem: required destination text, visible unless showLabels is false. |
| `countLabel` | `(label: string, count: number) => string` |  |  | Names an item that shows a count, replacing its accessible name: (label, count) => `{label}, {count} new` by default. Vietnamese: `{label}, {count} mục mới`. Optional. |
| `value` | `string` |  |  | On BottomNav: active destination href, usually supplied by the application router. Takes precedence over item active props. BottomNav does not change it on click. |
| `href` (required) | `string` |  |  | On BottomNavItem: destination URL and selection id. Required even with asChild. |
| `icon` (required) | `node` |  |  | On BottomNavItem: the regular 20px icon. |
| `activeIcon` (required) | `node` |  |  | On BottomNavItem: the filled icon for the active state. Supply the matching filled variant of the regular icon, at 20px. |
| `active` | `boolean` |  | `false` | On BottomNavItem: marks this destination current when BottomNav has no value. Selection still uses normal link navigation. |
| `count` | `number` |  |  | On BottomNavItem: a count above zero adds a badge and names the count through countLabel. Zero and negative counts are hidden. |
| `asChild` | `boolean` |  | `false` | On BottomNavItem: render the destination link behavior on its only child. |
| `onClick` | `React.MouseEventHandler<HTMLAnchorElement>` |  |  | On BottomNavItem: native link click handler. Use application navigation or preventDefault() for an in-place demonstration. |
| `showLabels` | `boolean` |  | `true` | On BottomNav: show destination labels below their icons. Set false to visually hide labels while keeping every link named for assistive technology. |

### Slots

| Name | Required | Description |
| --- | --- | --- |
| `children` | yes | 3 to 5 BottomNavItem with href, icon, activeIcon and label, plus optional count and active. |

## Anatomy

| Part | Description |
| --- | --- |
| `root` | Transparent fixed positioning wrapper, hidden from md up, with horizontal and bottom safe-area padding. Empty space around the capsule does not intercept pointer events. |
| `item` | One equal-width destination link: 20px icon over a font-size-xs label, with a rounded selection pill and at least size-element-xl height. |
| `icon` | The 20px outline icon, replaced with the required filled activeIcon when selected. |
| `label` | The destination text, visually hidden when showLabels is false. Long visible labels truncate. |
| `badge` | Optional solid danger count Badge on the icon, with text-on-danger. |
| `list` | Centered floating capsule on material-thin, with a maximum panel width, rounded-full corners, inner padding and a shadow. |
| `indicator` | One rounded pill that slides between equal-width destinations over duration-medium. It resizes with the bar; reduced motion switches selection without movement. |

## Accessibility

Role: `navigation`

| Keys | Action |
| --- | --- |
| `Tab` | Moves between destinations (links), in order. |
| `Enter` | Follows the link. |

- A nav landmark named by label. Items are links, not tabs: no role tab and no arrow-key roving.
- The active item has aria-current="page" and uses the Filled icon (DESIGN 9.1).
- Labels are visible by default. With showLabels=false, the label stays available to assistive technology through visually hidden text.
- A count Badge adds its number to the accessible name, through countLabel.

## Install

```bash
npx werewolf-ui add bottom-nav
```

Run `npx werewolf-ui init` once in your app before adding components. Init sets up tokens, their CSS import and agent rules. Add installs this component and its dependencies while preserving your theme. See [Installation](https://www.werewolf-ui.dev/docs/installation.md) for requirements and the alternative registry setup.
