Breadcrumb with Dropdowns

A breadcrumb trail where each non-terminal segment opens a dropdown of sibling items — switch workspace, project, or view from anywhere in the trail without leaving the page. Last segment is the current page. Inspired by Linear.

Navigation & SidebarsInspired by Linear
Live PreviewInteractive
Linear · Issue navigation

Click any chevron to switch to a sibling · Last segment is the current page (no dropdown)

BreadcrumbDropdowns.tsx
// Dependencies: react ^18, lucide-react
import React, { useState } from 'react';
import { ChevronDown, Check } from 'lucide-react';

function cn(...classes: (string | false | null | undefined)[]) {
  return classes.filter(Boolean).join(' ');
}

type Segment = {
  id: string;
  label: string;
  siblings?: string[];
};

const DEFAULT_TRAIL: Segment[] = [
  { id: 'workspace', label: 'Acme Corp', siblings: ['Acme Corp', 'Beta Studio', 'Demo Workspace'] },
  { id: 'project', label: 'Web Platform', siblings: ['Web Platform', 'Mobile App', 'Design System', 'Infrastructure'] },
  { id: 'view', label: 'In Progress', siblings: ['Backlog', 'In Progress', 'Done', 'All Issues'] },
  { id: 'current', label: 'ENG-204 · Login flow regression' },
];

export function BreadcrumbDropdowns() {
  const [trail, setTrail] = useState<Segment[]>(DEFAULT_TRAIL);
  const [openId, setOpenId] = useState<string | null>(null);

  const select = (segId: string, label: string) => {
    setTrail((prev) => prev.map((s) => (s.id === segId ? { ...s, label } : s)));
    setOpenId(null);
  };

  return (
    <div className="w-full max-w-[560px] bg-zinc-900 border border-white/[0.08] rounded-xl p-4">
      <nav className="flex items-center gap-1 flex-wrap text-sm">
        {trail.map((seg, i) => {
          const isLast = i === trail.length - 1;
          const isOpen = openId === seg.id;
          return (
            <div key={seg.id} className="flex items-center gap-1">
              {isLast || !seg.siblings ? (
                <span className="text-white font-medium px-2 py-1">{seg.label}</span>
              ) : (
                <div className="relative">
                  <button
                    onClick={() => setOpenId(isOpen ? null : seg.id)}
                    onBlur={() => setTimeout(() => setOpenId(null), 100)}
                    className={cn(
                      'flex items-center gap-1 px-2 py-1 rounded-md text-zinc-400 hover:text-white hover:bg-white/[0.05] transition-colors',
                      isOpen && 'text-white bg-white/[0.05]'
                    )}
                  >
                    {seg.label}
                    <ChevronDown
                      className={cn(
                        'w-3 h-3 text-zinc-500 transition-transform',
                        isOpen && 'rotate-180'
                      )}
                    />
                  </button>
                  {isOpen && (
                    <div className="absolute left-0 top-full mt-1 z-50 min-w-[200px] bg-[#1a1a1a] border border-[#2a2a2a] rounded-lg shadow-2xl py-1">
                      {seg.siblings.map((sib) => {
                        const isActive = sib === seg.label;
                        return (
                          <button
                            key={sib}
                            onMouseDown={(e) => {
                              e.preventDefault();
                              select(seg.id, sib);
                            }}
                            className={cn(
                              'w-full flex items-center justify-between gap-2 px-3 py-1.5 text-xs text-left transition-colors',
                              isActive
                                ? 'bg-indigo-500/15 text-indigo-200'
                                : 'text-zinc-300 hover:bg-white/[0.06] hover:text-white'
                            )}
                          >
                            <span>{sib}</span>
                            {isActive && <Check className="w-3 h-3 text-indigo-400 shrink-0" />}
                          </button>
                        );
                      })}
                    </div>
                  )}
                </div>
              )}
              {!isLast && <span className="text-zinc-600 px-0.5">/</span>}
            </div>
          );
        })}
      </nav>
    </div>
  );
}