Navigation rail

Navigation rails let people switch between UI views on mid-sized devices

Usage

Used for top-level destinations that need to be accessible anywhere in an app, navigation rails include 3 to 7 main destinations in tablet or desktop layouts.

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 NavigationRailRoot = 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 md:-inset-x-px md:inset-y-0 md:w-[88px] md:flex-col md:px-0 md:py-5',
      className
    )}
    {...props}
  />
))
NavigationRailRoot.displayName = 'NavigationRail'

const NavigationRailContainer = React.forwardRef<
  HTMLDivElement,
  React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
  <nav
    ref={ref}
    className={cn(
      'flex grow items-center justify-around md:h-fit md:w-full md:grow-0 md:flex-col md:space-y-5',
      className
    )}
    {...props}
  />
))
NavigationRailContainer.displayName = 'NavigationRailContainer'

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 NavigationRailItemIcon = 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>
))
NavigationRailItemIcon.displayName = 'NavigationRailItemIcon'

const NavigationRailItemLabel = 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>
))
NavigationRailItemLabel.displayName = 'NavigationRailItemLabel'

interface NavigationRailItemProps
  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 NavigationRailItem = React.forwardRef<
  HTMLAnchorElement,
  NavigationRailItemProps
>(({ className, active, asChild, ...props }, ref) => {
  const Comp = asChild ? Slot : 'a'

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

const NavigationRail = Object.assign(NavigationRailRoot, {
  Container: NavigationRailContainer,
  Item: NavigationRailItem,
  ItemIcon: NavigationRailItemIcon,
  ItemLabel: NavigationRailItemLabel,
})

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

export {
  NavigationRail,
  NavigationRailRoot,
  NavigationRailContainer,
  NavigationRailItem,
  NavigationRailItemIcon,
  NavigationRailItemLabel,
  styles,
}

Update the import paths to match your project setup.

Examples

Navigation rail destinations can be aligned as a group to the top, bottom, or center of a layout. On tablets, bottom alignment can make destinations easier for a user to reach with their thumbs.

By default the Navigation Rail will adapt as a Navigation Bar for small screens.

Maps

import { useState } from 'react'

import { Fab } from '@/components/ui/fab'
import { Icon } from '@/components/ui/icon'
import { IconButton } from '@/components/ui/icon-button'
import { NavigationRail } from '@/components/ui/navigation-rail'

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

const navigationItems = [
  navigationItem('maps', 'location_on', 'Maps'),
  navigationItem('places', 'home_work', 'Places'),
  navigationItem('favorites', 'bookmark', 'Favorites'),
  navigationItem('add', 'add_circle', 'Add'),
]

export const NavigationRailExample = () => {
  const [active, setActive] = useState('maps')

  return (
    <div class="relative flex w-full flex-col-reverse md:flex-row">
      <NavigationRail className="relative">
        <NavigationRail.Container className="mb-14 hidden md:flex md:space-y-4">
          <IconButton variant="standard">
            <Icon symbol="menu" />
          </IconButton>
          <Fab className="shadow-none hover:shadow-none">
            <Icon symbol="directions_alt" />
          </Fab>
        </NavigationRail.Container>
        <NavigationRail.Container>
          {navigationItems.map(({ id, icon, label }) => (
            <NavigationRail.Item key={id} asChild active={id === active}>
              <button onClick={() => setActive(id)}>
                <NavigationRail.ItemIcon>
                  <Icon symbol={icon} />
                </NavigationRail.ItemIcon>
                <NavigationRail.ItemLabel>{label}</NavigationRail.ItemLabel>
              </button>
            </NavigationRail.Item>
          ))}
        </NavigationRail.Container>
      </NavigationRail>
      <div className="relative grow">
        <div className="bg-background sticky top-0 flex h-16 items-center px-3 md:hidden">
          <IconButton variant="standard">
            <Icon symbol="menu" />
          </IconButton>
        </div>
        <div className="p-4">
          <h1 className="text-display-sm">
            {navigationItems.find(({ id }) => id === active)?.label}
          </h1>
        </div>
        <Fab className="absolute bottom-4 right-4 md:hidden">
          <Icon symbol="directions_alt" />
        </Fab>
      </div>
    </div>
  )
}