All components

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.

1"use client";
2
3import { 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";
14
15import {
16 MotionBreadcrumb,
17 type MotionBreadcrumbItem,
18} from "@/registry/ui/motion-breadcrumb";
19import { cn } from "@/lib/utils";
20
21type SitePage = {
22 id: string;
23 label: string;
24 description: string;
25 icon: LucideIcon;
26 children?: SitePage[];
27};
28
29const 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};
76
77function findPath(
78 node: SitePage,
79 targetId: string,
80 trail: SitePage[] = [],
81): SitePage[] | null {
82 const nextTrail = [...trail, node];
83
84 if (node.id === targetId) {
85 return nextTrail;
86 }
87
88 for (const child of node.children ?? []) {
89 const match = findPath(child, targetId, nextTrail);
90 if (match) {
91 return match;
92 }
93 }
94
95 return null;
96}
97
98function 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}
107
108export function MotionBreadcrumbExample() {
109 const [currentId, setCurrentId] = useState("settings");
110
111 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;
116
117 const items = toBreadcrumbItems(path);
118
119 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 path
125 </p>
126 <MotionBreadcrumb
127 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>
136
137 <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>
149
150 {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 right
154 </p>
155 <div className="grid gap-2 sm:grid-cols-2">
156 {childPages.map((child) => {
157 const ChildIcon = child.icon;
158 return (
159 <button
160 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 see
184 items slide out to the left.
185 </p>
186 )}
187 </div>
188 </div>
189
190 <div className="flex flex-wrap items-center gap-2">
191 <button
192 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 level
198 </button>
199 <button
200 type="button"
201 onClick={() => setCurrentId("home")}
202 className="rounded-md border px-3 py-1.5 text-sm"
203 >
204 Jump to Home
205 </button>
206 <button
207 type="button"
208 onClick={() => setCurrentId("notifications")}
209 className="rounded-md border px-3 py-1.5 text-sm"
210 >
211 Jump to Notifications
212 </button>
213 </div>
214
215 <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>. Mobile
218 collapses to parent + current with the same directional slide. With six
219 levels and{" "}
220 <code className="rounded bg-muted px-1 py-0.5">maxItems=4</code>, the
221 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

NameTypeDefaultDescription
itemsarrayRequiredThe items of the breadcrumb
classNamestringundefinedThe class name of the breadcrumb
listClassNamestringundefinedThe class name of the breadcrumb list
itemClassNamestringundefinedThe class name of the breadcrumb item
currentClassNamestringundefinedThe class name of the current breadcrumb item
ariaLabelstringundefinedThe aria label of the breadcrumb
separatorReactNodeundefinedThe separator of the breadcrumb
maxItemsnumber4The maximum number of items in the breadcrumb
expandedbooleanfalseWhether the breadcrumb is expanded
defaultExpandedbooleanfalseWhether the breadcrumb is expanded by default
onExpandedChangefunctionundefinedThe function to handle the expanded change
directionstringautoThe direction of the breadcrumb
animationobjectundefinedThe animation of the breadcrumb
collapseLabelstringShow all breadcrumb itemsThe label to show when the breadcrumb is collapsed
linkComponentReact.ComponentType<{ href: string; className?: string; onClick?: (event: React.MouseEvent<HTMLElement>) => void; children?: React.ReactNode; 'aria-label'?: string; }>undefinedThe component to use for the link
onNavigatefunctionundefinedThe function to handle the navigate