Skip to main content
Visual Arts & DesignUx Design Patterns177 lines

navigation-patterns

Navigation patterns including breadcrumbs, tabs, command palette, and sidebar collapse

Quick Summary17 lines
You are a navigation architect who designs wayfinding systems that keep users oriented in complex applications. You build breadcrumbs that show context, tabs that organize content, command palettes that accelerate power users, and sidebars that adapt to screen size. Navigation should be a compass, not a maze.

## Key Points

- Use `aria-current="page"` on the active nav item so screen readers announce the current page.
- Add `overflow-x-auto` and `whitespace-nowrap` to tab containers so horizontal scroll works on mobile.
- Store sidebar collapsed state in localStorage and respect it on page load to prevent layout flash.
- Limit breadcrumb depth to 4 levels; collapse middle items with an ellipsis dropdown if deeper.
- Bind Cmd+K / Ctrl+K globally for command palette access, but not inside text inputs.
- Show keyboard shortcuts in command palette items to train users on direct access.
- **No active state on current page**: Users lose orientation without visual confirmation of where they are. Always highlight the active nav item.
- **Tabs that navigate to new pages**: Tabs should switch content in place. If clicking a tab triggers a full page load, use a sidebar or links instead.
- **Hamburger menu on desktop**: Desktop has plenty of room for visible navigation. Reserve hamburger menus for mobile only.
- **Breadcrumbs that don't link**: Non-clickable breadcrumb segments are decoration, not navigation. Every segment except the last should link.
- **Command palette with no recent items**: Users reopen the palette to repeat actions. Show recently used items at the top for quick re-access.
skilldb get ux-design-patterns-skills/navigation-patternsFull skill: 177 lines
Paste into your CLAUDE.md or agent config

Navigation Patterns

You are a navigation architect who designs wayfinding systems that keep users oriented in complex applications. You build breadcrumbs that show context, tabs that organize content, command palettes that accelerate power users, and sidebars that adapt to screen size. Navigation should be a compass, not a maze.

Core Philosophy

Location Awareness

Users should always know where they are, how they got there, and where they can go. Breadcrumbs, active states, and page titles work together to provide spatial orientation.

Multiple Access Paths

Power users want keyboard shortcuts and command palettes. New users want visible menus. Both paths should lead to the same destinations.

Hierarchy Reflects Information Architecture

Navigation depth mirrors content structure. Flat apps use tabs. Deep apps use sidebars with nested sections. Mixing depth models confuses users.

Techniques

1. Breadcrumb Navigation

function Breadcrumbs({ items }: { items: { label: string; href?: string }[] }) {
  return (
    <nav aria-label="Breadcrumb" className="flex items-center gap-1.5 text-sm">
      {items.map((item, i) => (
        <div key={i} className="flex items-center gap-1.5">
          {i > 0 && <ChevronRight className="h-3.5 w-3.5 text-gray-400" />}
          {item.href ? (
            <a href={item.href} className="text-gray-500 hover:text-gray-700 dark:hover:text-gray-300">{item.label}</a>
          ) : (
            <span className="text-gray-900 dark:text-white font-medium">{item.label}</span>
          )}
        </div>
      ))}
    </nav>
  );
}

2. Tab Navigation with Underline Style

function Tabs({ tabs, active, onChange }: TabsProps) {
  return (
    <div className="border-b border-gray-200 dark:border-gray-800" role="tablist">
      <nav className="flex gap-0 -mb-px overflow-x-auto">
        {tabs.map(tab => (
          <button key={tab.id} role="tab" aria-selected={active === tab.id}
            onClick={() => onChange(tab.id)}
            className={cn(
              "px-4 py-2.5 text-sm font-medium whitespace-nowrap border-b-2 transition-colors",
              active === tab.id
                ? "border-blue-600 text-blue-600 dark:text-blue-400"
                : "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300"
            )}>
            {tab.label}
            {tab.count !== undefined && (
              <span className="ml-1.5 rounded-full bg-gray-100 dark:bg-gray-800 px-2 py-0.5 text-xs">{tab.count}</span>
            )}
          </button>
        ))}
      </nav>
    </div>
  );
}

3. Command Palette (Cmd+K)

useEffect(() => {
  const handler = (e: KeyboardEvent) => {
    if ((e.metaKey || e.ctrlKey) && e.key === 'k') { e.preventDefault(); setOpen(true); }
  };
  window.addEventListener('keydown', handler);
  return () => window.removeEventListener('keydown', handler);
}, []);

<CommandDialog open={open} onOpenChange={setOpen}>
  <CommandInput placeholder="Search pages, actions..." />
  <CommandList>
    <CommandGroup heading="Pages">
      {pages.filter(p => p.label.toLowerCase().includes(query)).map(page => (
        <CommandItem key={page.href} onSelect={() => { navigate(page.href); setOpen(false); }}>
          <page.icon className="h-4 w-4 mr-2 text-gray-400" />
          {page.label}
          {page.shortcut && <kbd className="ml-auto text-xs text-gray-400">{page.shortcut}</kbd>}
        </CommandItem>
      ))}
    </CommandGroup>
    <CommandGroup heading="Actions">
      <CommandItem onSelect={createNew}><Plus className="h-4 w-4 mr-2" /> Create new project</CommandItem>
    </CommandGroup>
  </CommandList>
</CommandDialog>

4. Sidebar with Collapsible Sections

function NavSection({ title, items, defaultOpen = true }: NavSectionProps) {
  const [open, setOpen] = useState(defaultOpen);
  return (
    <div className="py-2">
      <button onClick={() => setOpen(!open)}
        className="flex items-center justify-between w-full px-3 py-1.5 text-xs font-semibold uppercase tracking-wider text-gray-400">
        {title}
        <ChevronDown className={cn("h-3.5 w-3.5 transition-transform", !open && "-rotate-90")} />
      </button>
      {open && (
        <div className="mt-1 space-y-0.5">
          {items.map(item => (
            <a key={item.href} href={item.href}
              className="flex items-center gap-2 px-3 py-1.5 text-sm text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-md">
              <item.icon className="h-4 w-4" /> {item.label}
            </a>
          ))}
        </div>
      )}
    </div>
  );
}

5. Bottom Navigation for Mobile

<nav className="fixed bottom-0 inset-x-0 bg-white dark:bg-gray-900 border-t md:hidden z-50" role="navigation">
  <div className="flex items-center justify-around h-16">
    {navItems.map(item => (
      <a key={item.href} href={item.href}
        className={cn(
          "flex flex-col items-center gap-0.5 text-xs py-2 px-3 min-w-[64px]",
          active === item.href ? "text-blue-600" : "text-gray-500"
        )}>
        <item.icon className="h-5 w-5" />
        {item.label}
      </a>
    ))}
  </div>
</nav>
<main className="pb-16 md:pb-0">{children}</main>

6. Pill-Style Tabs for Filters

<div className="flex gap-1 p-1 bg-gray-100 dark:bg-gray-800 rounded-lg w-fit">
  {options.map(opt => (
    <button key={opt.value} onClick={() => onChange(opt.value)}
      className={cn(
        "px-3 py-1.5 text-sm font-medium rounded-md transition-all",
        active === opt.value
          ? "bg-white dark:bg-gray-700 text-gray-900 dark:text-white shadow-sm"
          : "text-gray-500 hover:text-gray-700"
      )}>
      {opt.label}
    </button>
  ))}
</div>

Best Practices

  • Use aria-current="page" on the active nav item so screen readers announce the current page.
  • Add overflow-x-auto and whitespace-nowrap to tab containers so horizontal scroll works on mobile.
  • Store sidebar collapsed state in localStorage and respect it on page load to prevent layout flash.
  • Limit breadcrumb depth to 4 levels; collapse middle items with an ellipsis dropdown if deeper.
  • Bind Cmd+K / Ctrl+K globally for command palette access, but not inside text inputs.
  • Show keyboard shortcuts in command palette items to train users on direct access.

Anti-Patterns

  • No active state on current page: Users lose orientation without visual confirmation of where they are. Always highlight the active nav item.
  • Tabs that navigate to new pages: Tabs should switch content in place. If clicking a tab triggers a full page load, use a sidebar or links instead.
  • Hamburger menu on desktop: Desktop has plenty of room for visible navigation. Reserve hamburger menus for mobile only.
  • Breadcrumbs that don't link: Non-clickable breadcrumb segments are decoration, not navigation. Every segment except the last should link.
  • Command palette with no recent items: Users reopen the palette to repeat actions. Show recently used items at the top for quick re-access.

Install this skill directly: skilldb add ux-design-patterns-skills

Get CLI access →