Motion Breadcrumb
A breadcrumb component that uses motion to animate the breadcrumb items.
Default
Interactive site tree with page content, child links, and jump controls to show forward, backward, and collapsed breadcrumb motion.
Current path
Settings
Notification and privacy preferences.
Go deeper - items slide in from the right
Desktop animates every crumb with popLayout. Mobile collapses to parent + current with the same directional slide. With six levels and maxItems=4, the middle crumbs collapse into an ellipsis until expanded.
example.tsx
1"use client";23import { useMemo, useState } from "react";4import {5 Bell,6 ChevronRight,7 Folder,8 Home,9 Layers,10 Settings,11 User,12 type LucideIcon,13} from "lucide-react";1415import {16 MotionBreadcrumb,17 type MotionBreadcrumbItem,18} from "@/registry/ui/motion-breadcrumb";19import { cn } from "@/lib/utils";2021type SitePage = {22 id: string;23 label: string;24 description: string;25 icon: LucideIcon;26 children?: SitePage[];27};2829const SITE: SitePage = {30 id: "home",31 label: "Home",32 description: "Pick a section to drill into the hierarchy.",33 icon: Home,34 children: [35 {36 id: "projects",37 label: "Projects",38 description: "Browse workspaces and open a project.",39 icon: Folder,40 children: [41 {42 id: "design-system",43 label: "Design System",44 description: "Shared tokens, patterns, and UI primitives.",45 icon: Layers,46 children: [47 {48 id: "account",49 label: "Account",50 description: "Profile, billing, and workspace access.",51 icon: User,52 children: [53 {54 id: "settings",55 label: "Settings",56 description: "Notification and privacy preferences.",57 icon: Settings,58 children: [59 {60 id: "notifications",61 label: "Notifications",62 description:63 "Fine-grained alerts for mentions, deploys, and billing.",64 icon: Bell,65 },66 ],67 },68 ],69 },70 ],71 },72 ],73 },74 ],75};7677function findPath(78 node: SitePage,79 targetId: string,80 trail: SitePage[] = [],81): SitePage[] | null {82 const nextTrail = [...trail, node];8384 if (node.id === targetId) {85 return nextTrail;86 }8788 for (const child of node.children ?? []) {89 const match = findPath(child, targetId, nextTrail);90 if (match) {91 return match;92 }93 }9495 return null;96}9798function toBreadcrumbItems(pages: SitePage[]): MotionBreadcrumbItem[] {99 return pages.map((page, index) => ({100 id: page.id,101 label: page.label,102 icon: page.icon,103 href: `#${page.id}`,104 ...(index === pages.length - 1 ? {} : { ariaLabel: `Go to ${page.label}` }),105 }));106}107108export function MotionBreadcrumbExample() {109 const [currentId, setCurrentId] = useState("settings");110111 const path = useMemo(() => findPath(SITE, currentId) ?? [SITE], [currentId]);112 const currentPage = path.at(-1) ?? SITE;113 const parentPage = path.at(-2);114 const childPages = currentPage.children ?? [];115 const CurrentIcon = currentPage.icon;116117 const items = toBreadcrumbItems(path);118119 return (120 <div className="mx-auto flex w-full max-w-3xl flex-col gap-6 p-6">121 <div className="overflow-hidden rounded-xl border border-border/70 bg-card shadow-sm">122 <div className="border-b border-border/60 bg-muted/20 px-4 py-3 sm:px-5">123 <p className="mb-2 text-xs font-medium uppercase tracking-wide text-muted-foreground">124 Current path125 </p>126 <MotionBreadcrumb127 items={items}128 maxItems={4}129 animation={{ duration: 0.28, distance: 14 }}130 onNavigate={(item, _index, event) => {131 event.preventDefault();132 setCurrentId(item.id);133 }}134 />135 </div>136137 <div className="space-y-5 px-4 py-5 sm:px-5">138 <div className="space-y-2">139 <div className="flex items-center gap-2 text-foreground">140 <CurrentIcon className="size-4 shrink-0 text-muted-foreground" />141 <h3 className="text-lg font-semibold tracking-tight">142 {currentPage.label}143 </h3>144 </div>145 <p className="text-sm leading-6 text-muted-foreground">146 {currentPage.description}147 </p>148 </div>149150 {childPages.length > 0 ? (151 <div className="space-y-2">152 <p className="text-xs font-medium text-muted-foreground">153 Go deeper - items slide in from the right154 </p>155 <div className="grid gap-2 sm:grid-cols-2">156 {childPages.map((child) => {157 const ChildIcon = child.icon;158 return (159 <button160 key={child.id}161 type="button"162 onClick={() => setCurrentId(child.id)}163 className={cn(164 "flex items-center justify-between gap-3 rounded-lg border border-border/60",165 "bg-background px-3 py-2.5 text-left text-sm transition-colors",166 "hover:border-border hover:bg-muted/40",167 )}168 >169 <span className="flex min-w-0 items-center gap-2">170 <ChildIcon className="size-3.5 shrink-0 text-muted-foreground" />171 <span className="truncate font-medium">172 {child.label}173 </span>174 </span>175 <ChevronRight className="size-3.5 shrink-0 text-muted-foreground" />176 </button>177 );178 })}179 </div>180 </div>181 ) : (182 <p className="rounded-lg border border-dashed border-border/70 px-3 py-2 text-sm text-muted-foreground">183 Deepest page in the demo tree. Use the breadcrumb or Back to see184 items slide out to the left.185 </p>186 )}187 </div>188 </div>189190 <div className="flex flex-wrap items-center gap-2">191 <button192 type="button"193 disabled={!parentPage}194 onClick={() => parentPage && setCurrentId(parentPage.id)}195 className="rounded-md border px-3 py-1.5 text-sm disabled:opacity-40"196 >197 Back one level198 </button>199 <button200 type="button"201 onClick={() => setCurrentId("home")}202 className="rounded-md border px-3 py-1.5 text-sm"203 >204 Jump to Home205 </button>206 <button207 type="button"208 onClick={() => setCurrentId("notifications")}209 className="rounded-md border px-3 py-1.5 text-sm"210 >211 Jump to Notifications212 </button>213 </div>214215 <p className="text-xs leading-5 text-muted-foreground">216 Desktop animates every crumb with{" "}217 <code className="rounded bg-muted px-1 py-0.5">popLayout</code>. Mobile218 collapses to parent + current with the same directional slide. With six219 levels and{" "}220 <code className="rounded bg-muted px-1 py-0.5">maxItems=4</code>, the221 middle crumbs collapse into an ellipsis until expanded.222 </p>223 </div>224 );225}
Installation & source
Install via the shadcn CLI or copy the registry files manually.
bash
npx shadcn@latest add @tt-ui/motion-breadcrumb
Props
| Name | Type | Default | Description |
|---|---|---|---|
| items | array | Required | The items of the breadcrumb |
| className | string | undefined | The class name of the breadcrumb |
| listClassName | string | undefined | The class name of the breadcrumb list |
| itemClassName | string | undefined | The class name of the breadcrumb item |
| currentClassName | string | undefined | The class name of the current breadcrumb item |
| ariaLabel | string | undefined | The aria label of the breadcrumb |
| separator | ReactNode | undefined | The separator of the breadcrumb |
| maxItems | number | 4 | The maximum number of items in the breadcrumb |
| expanded | boolean | false | Whether the breadcrumb is expanded |
| defaultExpanded | boolean | false | Whether the breadcrumb is expanded by default |
| onExpandedChange | function | undefined | The function to handle the expanded change |
| direction | string | auto | The direction of the breadcrumb |
| animation | object | undefined | The animation of the breadcrumb |
| collapseLabel | string | Show all breadcrumb items | The label to show when the breadcrumb is collapsed |
| linkComponent | React.ComponentType<{ href: string; className?: string; onClick?: (event: React.MouseEvent<HTMLElement>) => void; children?: React.ReactNode; 'aria-label'?: string; }> | undefined | The component to use for the link |
| onNavigate | function | undefined | The function to handle the navigate |