ephemera

Breadcrumb

A typed file path.

Install

$ npx shadcn@latest add https://ephemera.kerrshift.com/r/breadcrumb.json

Or copy the source below into components/ui/breadcrumb.tsx. Core components need the Paper Theme and Radix where noted.

Usage

example.tsx
import { Breadcrumb, BreadcrumbList, BreadcrumbItem, BreadcrumbLink, BreadcrumbPage, BreadcrumbSeparator } from "@/components/ui/breadcrumb";

<Breadcrumb>
  <BreadcrumbList>
    <BreadcrumbItem><BreadcrumbLink href="/">Drawer</BreadcrumbLink></BreadcrumbItem>
    <BreadcrumbSeparator />
    <BreadcrumbItem><BreadcrumbLink href="/letters">Letters</BreadcrumbLink></BreadcrumbItem>
    <BreadcrumbSeparator />
    <BreadcrumbItem><BreadcrumbPage>Dear Sam</BreadcrumbPage></BreadcrumbItem>
  </BreadcrumbList>
</Breadcrumb>

Props

PropTypeDefaultDescription
BreadcrumbLink asChildboolean—Use your router's Link.
BreadcrumbSeparator childrenReactNode"›"Custom separator.

Any other props are passed to the root element.

Fonts

Components read optional CSS variables and fall back to system fonts: --font-display, --font-receipt, --font-type, --font-hand and --font-stamp. This site uses Fraunces, IBM Plex Mono, Courier Prime, Caveat and Anton.

Source

components/ui/breadcrumb.tsx
import * as React from "react";
import { Slot } from "radix-ui";

const type = { fontFamily: "var(--font-type, 'Courier New', ui-monospace, monospace)" };

/** A typed file path: Drawer › Folder › Letter */
export function Breadcrumb(props: React.HTMLAttributes<HTMLElement>) {
  return <nav aria-label="breadcrumb" {...props} />;
}

export function BreadcrumbList({ className = "", style, ...props }: React.OlHTMLAttributes<HTMLOListElement>) {
  return (
    <ol
      className={`flex flex-wrap items-center gap-2 text-[12.5px] uppercase tracking-[0.12em] text-(--ink-muted) ${className}`}
      style={{ ...type, ...style }}
      {...props}
    />
  );
}

export function BreadcrumbItem({ className = "", ...props }: React.LiHTMLAttributes<HTMLLIElement>) {
  return <li className={`inline-flex items-center gap-2 ${className}`} {...props} />;
}

export function BreadcrumbLink({
  asChild,
  className = "",
  ...props
}: React.AnchorHTMLAttributes<HTMLAnchorElement> & { asChild?: boolean }) {
  const Comp = asChild ? Slot.Root : "a";
  return (
    <Comp
      className={`underline decoration-(--ink-faint) decoration-dashed underline-offset-4 transition-colors hover:text-(--ink) hover:decoration-(--pen) hover:decoration-solid ${className}`}
      {...props}
    />
  );
}

export function BreadcrumbPage({ className = "", ...props }: React.HTMLAttributes<HTMLSpanElement>) {
  return <span role="link" aria-disabled="true" aria-current="page" className={`font-bold text-(--ink) ${className}`} {...props} />;
}

export function BreadcrumbSeparator({ children, className = "", ...props }: React.LiHTMLAttributes<HTMLLIElement>) {
  return (
    <li role="presentation" aria-hidden className={`text-(--ink-faint) ${className}`} {...props}>
      {children ?? "›"}
    </li>
  );
}