Navigation bar

Navigation bars let people switch between UI views on smaller devices

Usage

Navigation bars (nav bars) provide access to three to five destinations.

Instalation

Run the following command:

npm i @radix-ui/react-slot

Copy and paste the following code into your project.

import * as React from 'react'
import { Slot } from '@radix-ui/react-slot'

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

const NavigationBarRoot = React.forwardRef<
  HTMLDivElement,
  React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
  <aside
    ref={ref}
    className={cn(
      'bottom-bar fixed inset-x-0 bottom-0 z-30 flex items-center bg-surfaceContainer py-4 pt-3',
      className
    )}
    {...props}
  />
))
NavigationBarRoot.displayName = 'NavigationBar'

const NavigationBarContainer = React.forwardRef<
  HTMLDivElement,
  React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
  <nav
    ref={ref}
    className={cn('flex grow items-center justify-around', className)}
    {...props}
  />
))
NavigationBarContainer.displayName = 'NavigationBarContainer'

const iconStyle =
  'group-data-[active]:duration-100 group-hover:font-emphasis z-0 group-data-[active]:font-filled group-hover:group-data-[active]:font-filled-emphasis'

const NavigationBarItemIcon = React.forwardRef<
  HTMLSpanElement,
  React.HTMLAttributes<HTMLSpanElement>
>(({ children, className, ...props }, ref) => (
  <span
    className={cn(
      'relative grid h-8 w-16 place-items-center rounded-lg text-onSurface before:absolute before:inset-0 before:z-0 before:hidden before:animate-zoom-in-x before:rounded-2xl before:bg-secondaryContainer before:transition-colors group-data-[active]:before:block md:w-14'
    )}
  >
    <Slot ref={ref} className={cn(iconStyle, className)} {...props}>
      {children}
    </Slot>
    <span className="absolute inset-0 z-0 rounded-2xl bg-onSurfaceVariant opacity-0 transition-opacity group-hover:opacity-8 group-focus:opacity-12 group-active:opacity-12" />
  </span>
))
NavigationBarItemIcon.displayName = 'NavigationBarItemIcon'

const NavigationBarItemLabel = React.forwardRef<
  HTMLSpanElement,
  React.HTMLAttributes<HTMLSpanElement>
>(({ className, children, ...props }, ref) => (
  <span
    ref={ref}
    className={cn(
      'w-full truncate text-center text-label-md font-normal text-onSurface group-hover:font-medium group-data-[active]:font-medium',
      className
    )}
    {...props}
  >
    {children}
  </span>
))
NavigationBarItemLabel.displayName = 'NavigationBarItemLabel'

interface NavigationBarItemProps
  extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
  active?: boolean
  asChild?: boolean
}

const itemStyle =
  'relative group grid w-full place-items-center gap-y-1 font-normal outline-none p-0.5'

const NavigationBarItem = React.forwardRef<
  HTMLAnchorElement,
  NavigationBarItemProps
>(({ className, active, asChild, ...props }, ref) => {
  const Comp = asChild ? Slot : 'a'

  return (
    <Comp
      ref={ref}
      data-active={active ? '' : undefined}
      className={cn(itemStyle, className)}
      {...props}
    />
  )
})
NavigationBarItem.displayName = 'NavigationBarItem'

const NavigationBar = Object.assign(NavigationBarRoot, {
  Container: NavigationBarContainer,
  Item: NavigationBarItem,
  ItemIcon: NavigationBarItemIcon,
  ItemLabel: NavigationBarItemLabel,
})

const styles = {
  icon: iconStyle,
  item: itemStyle,
}

export {
  NavigationBar,
  NavigationBarRoot,
  NavigationBarContainer,
  NavigationBarItem,
  NavigationBarItemIcon,
  NavigationBarItemLabel,
  styles,
}

Update the import paths to match your project setup.

Examples

Navigation bars (nav bars) provide access to three to five destinations.

The nav bar is positioned at the bottom of screens for convenient access. Each destination is represented by an icon and optional text label.

Recommended for small screens sizes, for bigger screens sizes see Navigation Rail.

News

import { useState } from 'react'

import { Fab } from '@/components/ui/fab'
import { Icon } from '@/components/ui/icon'
import { NavigationBar } from '@/components/ui/navigation-bar'

const navigationItem = (id: string, icon: string, label: string) => ({
  id,
  icon,
  label,
})

const navigationItems = [
  navigationItem('news', 'news', 'News'),
  navigationItem('global', 'globe', 'Global'),
  navigationItem('for-you', 'star', 'For you'),
  navigationItem('trending', 'trending_up', 'Trending'),
  navigationItem('archive', 'archive', 'Archive'),
]

export const NavigationBarExample = () => {
  const [active, setActive] = useState('news')

  return (
    <div className="relative flex w-full flex-col">
      <div className="relative grow">
        <div className="p-4">
          <h1 className="text-display-sm">
            {navigationItems.find(({ id }) => id === active)?.label}
          </h1>
        </div>
      </div>
      <NavigationBar>
        <NavigationBar.Container>
          {navigationItems.map(({ id, icon, label }) => (
            <NavigationBar.Item key={id} asChild active={id === active}>
              <button onClick={() => setActive(id)}>
                <NavigationBar.ItemIcon>
                  <Icon symbol={icon} />
                </NavigationBar.ItemIcon>
                <NavigationBar.ItemLabel>{label}</NavigationBar.ItemLabel>
              </button>
            </NavigationBar.Item>
          ))}
        </NavigationBar.Container>
      </NavigationBar>
    </div>
  )
}