# Mobile Bottom Nav

> Mobile bottom navigation with icons, badges and an optional raised center action.

- Author: Atelier UI
- License: MIT
- Page: https://chromotion.dev/en/components/mobile-bottom-nav
- Site: Atelier UI (https://chromotion.dev)

## Install

```bash
npx shadcn@latest add https://chromotion.dev/r/mobile-bottom-nav.json
```

Stack: React + TypeScript + Tailwind CSS v4, shadcn/ui conventions. `cn` comes from `@/lib/utils`.

## Dependencies

No extra dependencies.

## Usage

```tsx title="components/mobile-bottom-nav-demo.tsx"
"use client"

import * as React from "react"
import { Heart, House, Plus, Search, User } from "lucide-react"

import { MobileBottomNav, type BottomNavItem } from "@/components/mobile-bottom-nav"

const items: BottomNavItem[] = [
  { id: "home", label: "Home", icon: House },
  { id: "search", label: "Search", icon: Search },
  { id: "likes", label: "Likes", icon: Heart, badge: 3 },
  { id: "me", label: "Profile", icon: User },
]

export default function MobileBottomNavDemo() {
  const [tab, setTab] = React.useState("home")

  return (
    <div className="flex h-[420px] w-72 flex-col overflow-hidden rounded-[2rem] border-4 border-foreground/80 bg-card">
      <div className="grid flex-1 content-center justify-items-center gap-1 p-6 text-center">
        <p className="text-xs tracking-wide text-muted-foreground uppercase">Current tab</p>
        <p className="text-xl font-semibold capitalize">{tab}</p>
      </div>
      <MobileBottomNav items={items} value={tab} onValueChange={setTab} fab={{ label: "Create", icon: Plus }} />
    </div>
  )
}
```

## Source

```tsx title="components/mobile-bottom-nav.tsx"
"use client"

import * as React from "react"

import { cn } from "@/lib/utils"

export type BottomNavItem = {
  id: string
  label: string
  icon: React.ComponentType<{ className?: string }>
  /** Number shown on the icon; 0 or undefined hides it */
  badge?: number
}

export type BottomNavFab = { label: string; icon: React.ComponentType<{ className?: string }>; onClick?: () => void }

export interface MobileBottomNavProps extends Omit<React.ComponentProps<"nav">, "children" | "defaultValue" | "onChange"> {
  items: BottomNavItem[]
  value?: string
  defaultValue?: string
  onValueChange?: (id: string) => void
  /** Raised action button inserted in the middle */
  fab?: BottomNavFab
}

export function MobileBottomNav({ items, value, defaultValue, onValueChange, fab, className, ...props }: MobileBottomNavProps) {
  const [inner, setInner] = React.useState(defaultValue ?? items[0]?.id)
  const current = value ?? inner
  const half = Math.ceil(items.length / 2)

  const renderItem = (item: BottomNavItem) => {
    const selected = item.id === current
    const Icon = item.icon
    return (
      <button
        key={item.id}
        type="button"
        aria-current={selected ? "page" : undefined}
        onClick={() => {
          if (value === undefined) setInner(item.id)
          onValueChange?.(item.id)
        }}
        className="group flex flex-1 flex-col items-center gap-1 py-2 text-[11px] font-medium text-muted-foreground transition-colors aria-[current=page]:text-primary focus-visible:outline-none"
      >
        <span className="relative flex h-8 w-14 items-center justify-center rounded-full transition-colors duration-200 group-aria-[current=page]:bg-primary/12 group-focus-visible:ring-2 group-focus-visible:ring-ring">
          <Icon className="size-5 transition-transform duration-200 group-active:scale-90" />
          {!!item.badge && (
            <span className="absolute top-0 right-3 flex h-4 min-w-4 items-center justify-center rounded-full bg-destructive px-1 text-[10px] font-semibold text-white tabular-nums">
              {item.badge}
            </span>
          )}
        </span>
        {item.label}
      </button>
    )
  }

  const FabIcon = fab?.icon

  return (
    <nav aria-label="Primary" className={cn("flex items-end border-t bg-background px-2 pb-2 text-foreground", className)} {...props}>
      {items.slice(0, fab ? half : items.length).map(renderItem)}
      {fab && FabIcon && (
        <button
          type="button"
          aria-label={fab.label}
          onClick={fab.onClick}
          className="mx-2 -mt-6 mb-3 flex size-14 shrink-0 items-center justify-center rounded-full bg-primary text-primary-foreground shadow-lg shadow-primary/30 transition-transform hover:scale-105 active:scale-95 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background focus-visible:outline-none"
        >
          <FabIcon className="size-6" />
        </button>
      )}
      {fab && items.slice(half).map(renderItem)}
    </nav>
  )
}
```
