Breadcrumb Component

Displays the path to the current resource using a hierarchy of links.

Installation

pnpm dlx shadcn@latest add https://ui.vllnt.com/r/breadcrumb.json
bash

Usage

import { Breadcrumb } from '@vllnt/ui'

export function BreadcrumbExample() {
return <Breadcrumb items={[{ href: '/', label: 'Home' }, { label: 'Current Page' }]} />
}
tsx

Code

import Link from "next/link";
import type * as React from "react";

import { cn } from "../../lib/utils";

export type BreadcrumbItem = {
  href?: string;
  icon?: React.ReactNode;
  label: React.ReactNode;
};

type BreadcrumbProps = {
  className?: string;
  items: BreadcrumbItem[];
  separator?: "arrow" | "chevron" | "slash";
  showHomeIcon?: boolean;
  variant?: "default" | "minimal";
};

const SEPARATOR_CHARS: Record<string, string> = {
  arrow: "→",
  chevron: "›",
  slash: "/",
};

function SeparatorIcon({ type }: { type: string }) {
  return (
    <span aria-hidden="true" className="text-muted-foreground">
      {SEPARATOR_CHARS[type] ?? "›"}
    </span>
  );
}

export function Breadcrumb({
  className,
  items,
  separator = "chevron",
  variant = "default",
}: BreadcrumbProps) {
  return (
    <nav
      aria-label="Breadcrumb"
      className={cn("flex items-center space-x-1 text-sm", className)}
    >
      {items.map((item, index) => (
        <div className="flex items-center" key={index}>
          {index > 0 && (
            <span className="mx-2">
              <SeparatorIcon type={separator} />
            </span>
          )}

          {item.href ? (
            <Link
              className="flex items-center gap-1 hover:text-foreground transition-colors"
              href={item.href}
            >
              {item.icon ? (
                <span className="flex-shrink-0">{item.icon}</span>
              ) : null}
              <span
                className={cn(
                  // Truncate plain string labels; custom React elements handle their own overflow
                  typeof item.label === "string" && "truncate",
                  variant === "minimal"
                    ? "text-muted-foreground"
                    : "text-foreground",
                )}
              >
                {item.label}
              </span>
            </Link>
          ) : (
            <span className="flex items-center gap-1 text-muted-foreground">
              {item.icon ? (
                <span className="flex-shrink-0">{item.icon}</span>
              ) : null}
              <span
                className={cn(typeof item.label === "string" && "truncate")}
              >
                {item.label}
              </span>
            </span>
          )}
        </div>
      ))}
    </nav>
  );
}
typescript