Shared UINavigation

Breadcrumb.

A path with context. Resolve route patterns, name dynamic resources, and keep long journeys within reach.

Automatic routesCustom hierarchyResponsive overflow

Follow the path

One trail. Your application's rules.

Pick a scenario and change the data state, unknown-segment policy, and visible slots. The breadcrumb in this site's top bar is the same component resolving the real pathname.

PATH SANDBOXRouter independent
/workspace/projects/atlas/tasks/task-42/activity

Links update this local path only. Dynamic names come from demo data; no request is made.

DYNAMIC DATA
UNKNOWN SEGMENTS
VISIBLE SLOTS
Resolved items
[
  {
    "id": "/",
    "label": "Home",
    "href": "/",
    "current": false
  },
  {
    "id": "/workspace",
    "label": "Workspace",
    "href": "/workspace",
    "current": false
  },
  {
    "id": "/workspace/projects",
    "label": "Projects",
    "href": "/workspace/projects",
    "current": false
  },
  {
    "id": "project",
    "label": "Atlas",
    "href": "/workspace/projects/atlas",
    "current": false
  },
  {
    "id": "/workspace/projects/:projectId/tasks",
    "label": "Tasks",
    "href": "/workspace/projects/atlas/tasks",
    "current": false
  },
  {
    "id": "/workspace/projects/:projectId/tasks/:taskId",
    "label": "Review the component library",
    "href": "/workspace/projects/atlas/tasks/task-42",
    "current": false
  },
  {
    "id": "/workspace/projects/:projectId/tasks/:taskId/activity",
    "label": "Activity",
    "href": "/workspace/projects/atlas/tasks/task-42/activity",
    "current": true
  }
]

Route metadata

Names belong to your data.

Read pathname from your router and use its Link in renderLink. Label functions are synchronous; loading and missing-data labels are explicit. API calls, caching, permissions, and translations stay in your application.

header.tsx
import Link from "next/link";
import { usePathname } from "next/navigation";
import { AppBreadcrumb, type BreadcrumbRoute } from "@/components/ui/app-breadcrumb";

type RouteData = { project: { name: string } };

const routes: BreadcrumbRoute<RouteData>[] = [
  { path: "/", label: "Home" },
  { path: "/projects", label: "Projects" },
  {
    path: "/projects/:projectId",
    label: ({ data }) => data?.project.name,
    fallbackLabel: "Project unavailable",
  },
];

export function Header({ project, isLoading }) {
  const pathname = usePathname();
  return (
    <AppBreadcrumb
      pathname={pathname}
      routes={routes}
      data={{ project }}
      loading={isLoading}
      maxVisibleItems={5}
      compactMaxVisibleItems={3}
      renderLink={({ href, children }) => <Link href={href}>{children}</Link>}
    />
  );
}
hierarchy options
// A hierarchy that differs from URL nesting.
{ id: "project", path: "/projects/:projectId", label: projectLabel }
{ path: "/reports/:projectId/weekly", parentId: "project", label: "Weekly report" }

// Skip a level but keep its ancestors.
{ path: "/workspace", label: "Workspace", hidden: true }

// Start a new hierarchy; no URL ancestors are rendered.
{ path: "/admin", label: "Admin", parentId: null }

// Resolve without rendering, for a custom view.
const items = resolveBreadcrumbs({ pathname, routes, data });

Static segments take priority over :parameters, then terminal *catchAll patterns. Invalid patterns, duplicate IDs, cyclic parents, and unsafe links fail with configuration errors.

Layout + page

Let the page name its own trail.

A layout renders the provider and the breadcrumb. A nested page publishes labels for the resources it loaded. Scope includes route parameters, so an inactive page can never change the current trail.

SHARED LAYOUTActive scope: project:atlas

The layout and page publishers are siblings. Unmounting the active page restores the provider fallback.

layout.tsx and page.tsx
// Layout: scope identifies the active route instance.
<BreadcrumbProvider scope={routeKey} pathname={pathname} routes={routes}>
  <header><ContextBreadcrumb /></header>
  {children}
</BreadcrumbProvider>

// A deeply nested page publishes a full trail, or only the labels it knows.
usePageBreadcrumbs({ scope: routeKey, routeLabels: { project: project.name } });
// Or: usePageBreadcrumbs({ scope: routeKey, items });

Explicit when needed

Manual items

When there is no router, or the trail is not derived from the URL, pass items directly. The last item is current.

Visible-item limits include the overflow trigger. The first and current items remain visible; intermediate links move into a keyboard-accessible menu. Long labels truncate visually while their full text remains in the markup and title.

Reference

Props

AppBreadcrumb accepts either a resolved mode (pathname and routes) or a manual mode (items). BreadcrumbProvider takes the same props plus scope.

PropTypeDefault
pathname

The current location from your router.

string—
routes

Route patterns to resolve against.

BreadcrumbRoute<TData>[]—
data

Loaded data passed to label and href resolvers.

TData—
loading

Marks function labels pending. Static labels stay visible.

boolean—
unknown

Policy for segments with no matching route.

"format" | "omit" | "error" | (context) => ReactNode"format"
items

Manual trail. Mutually exclusive with pathname and routes.

BreadcrumbEntry[]—
maxVisibleItems

Visible slots including the overflow trigger. Minimum 3.

number5
compactMaxVisibleItems

Visible slots below 768px.

number3
separator

Rendered between items.

ReactNode—
renderLink

Use your router's Link.

({ href, children, item }) => ReactElement—
labels

Accessible names and fallback strings.

Partial<BreadcrumbLabels>—
PropTypeDefault
path

Pattern with static segments, :params, and a terminal *catchAll.

string—
label

Static text or a synchronous resolver receiving params, data, pathname, and path.

ReactNode | (context) => ReactNode—
id

Referenced by parentId to build custom hierarchies.

stringpath
parentId

undefined follows URL prefixes; null starts a new hierarchy.

string | null—
hidden

Skip this level but keep its ancestors.

booleanfalse
href

Override the navigable URL. null renders a non-navigable ancestor.

string | null | (context) => string | null—
loadingLabel

Shown while loading applies to a function label.

ReactNode—
fallbackLabel

Shown when a function label returns nothing.

ReactNode—