Breadcrumb
Icon-capable breadcrumb trail.
Trail
An icon-capable breadcrumb trail.
function BreadcrumbDemo() {
return (
<Breadcrumb>
<BreadcrumbItem icon={RiHomeLine} href="#">
Home
</BreadcrumbItem>
<BreadcrumbItem icon={RiFolderLine} href="#">
Projects
</BreadcrumbItem>
<BreadcrumbItem>Design system</BreadcrumbItem>
</Breadcrumb>
);
}function BreadcrumbDemo() {
return (
<Breadcrumb>
<BreadcrumbItem icon={RiHomeLine} href="#">
Home
</BreadcrumbItem>
<BreadcrumbItem icon={RiFolderLine} href="#">
Projects
</BreadcrumbItem>
<BreadcrumbItem>Design system</BreadcrumbItem>
</Breadcrumb>
);
}Installation
npx shadcn@latest add https://boardcn.dev/r/breadcrumb.jsonnpx shadcn@latest add https://boardcn.dev/r/breadcrumb.jsonBoardCN dependencies
The CLI installs these for you — you do not need to add them yourself.
Source
The file the CLI copies into your project.
import { Children, Fragment, isValidElement } from "react";
import type { ComponentType, ReactNode, Ref } from "react";
import { ChevronRightSmall } from "@/components/foundations/icons/chevrons";
import { cx } from "@/utils/cx";
type IconComponent = ComponentType<{
className?: string;
"aria-hidden"?: boolean | "true" | "false";
}>;
export interface BreadcrumbProps {
children: ReactNode;
"aria-label"?: string;
className?: string;
ref?: Ref<HTMLElement>;
}
export function Breadcrumb({
children,
"aria-label": ariaLabel = "Breadcrumb",
className,
ref,
}: BreadcrumbProps) {
const items = Children.toArray(children).filter(isValidElement);
return (
<nav
ref={ref}
aria-label={ariaLabel}
className={cx(
"flex w-full items-center overflow-x-auto [scrollbar-width:none] [&::-webkit-scrollbar]:hidden",
className,
)}
>
<ol className="flex items-center gap-2.5 px-1">
{items.map((item, index) => (
<Fragment key={item.key ?? index}>
{index > 0 && (
<ChevronRightSmall className="size-3 shrink-0 text-text-tertiary" aria-hidden />
)}
{item}
</Fragment>
))}
</ol>
</nav>
);
}
export interface BreadcrumbItemProps {
children?: ReactNode;
/** Optional leading icon (16px), inherits the item color. */
icon?: IconComponent;
href?: string;
onClick?: () => void;
/** Marks the active page: not interactive, darker text. */
current?: boolean;
className?: string;
}
export function BreadcrumbItem({
children,
icon: Icon,
href,
onClick,
current = false,
className,
}: BreadcrumbItemProps) {
const content = (
<>
{Icon && <Icon className="size-4 shrink-0" aria-hidden />}
{children}
</>
);
if (current) {
return (
<li
aria-current="page"
className={cx(
"flex items-center gap-1.5 text-caption-1-medium whitespace-nowrap text-text-secondary",
className,
)}
>
{content}
</li>
);
}
const interactiveClass = cx(
"-mx-1 flex items-center gap-1.5 rounded-md px-1 py-0.5 text-caption-1-medium whitespace-nowrap text-text-tertiary",
"transition-colors duration-150 ease outline-none",
"hover:bg-background-primary-hover hover:text-text-secondary",
"focus-visible:ring-2 focus-visible:ring-border-focus-ring",
className,
);
return (
<li className="flex items-center">
{href ? (
<a href={href} className={interactiveClass}>
{content}
</a>
) : (
<button type="button" onClick={onClick} className={cx("cursor-pointer", interactiveClass)}>
{content}
</button>
)}
</li>
);
}import { Children, Fragment, isValidElement } from "react";
import type { ComponentType, ReactNode, Ref } from "react";
import { ChevronRightSmall } from "@/components/foundations/icons/chevrons";
import { cx } from "@/utils/cx";
type IconComponent = ComponentType<{
className?: string;
"aria-hidden"?: boolean | "true" | "false";
}>;
export interface BreadcrumbProps {
children: ReactNode;
"aria-label"?: string;
className?: string;
ref?: Ref<HTMLElement>;
}
export function Breadcrumb({
children,
"aria-label": ariaLabel = "Breadcrumb",
className,
ref,
}: BreadcrumbProps) {
const items = Children.toArray(children).filter(isValidElement);
return (
<nav
ref={ref}
aria-label={ariaLabel}
className={cx(
"flex w-full items-center overflow-x-auto [scrollbar-width:none] [&::-webkit-scrollbar]:hidden",
className,
)}
>
<ol className="flex items-center gap-2.5 px-1">
{items.map((item, index) => (
<Fragment key={item.key ?? index}>
{index > 0 && (
<ChevronRightSmall className="size-3 shrink-0 text-text-tertiary" aria-hidden />
)}
{item}
</Fragment>
))}
</ol>
</nav>
);
}
export interface BreadcrumbItemProps {
children?: ReactNode;
/** Optional leading icon (16px), inherits the item color. */
icon?: IconComponent;
href?: string;
onClick?: () => void;
/** Marks the active page: not interactive, darker text. */
current?: boolean;
className?: string;
}
export function BreadcrumbItem({
children,
icon: Icon,
href,
onClick,
current = false,
className,
}: BreadcrumbItemProps) {
const content = (
<>
{Icon && <Icon className="size-4 shrink-0" aria-hidden />}
{children}
</>
);
if (current) {
return (
<li
aria-current="page"
className={cx(
"flex items-center gap-1.5 text-caption-1-medium whitespace-nowrap text-text-secondary",
className,
)}
>
{content}
</li>
);
}
const interactiveClass = cx(
"-mx-1 flex items-center gap-1.5 rounded-md px-1 py-0.5 text-caption-1-medium whitespace-nowrap text-text-tertiary",
"transition-colors duration-150 ease outline-none",
"hover:bg-background-primary-hover hover:text-text-secondary",
"focus-visible:ring-2 focus-visible:ring-border-focus-ring",
className,
);
return (
<li className="flex items-center">
{href ? (
<a href={href} className={interactiveClass}>
{content}
</a>
) : (
<button type="button" onClick={onClick} className={cx("cursor-pointer", interactiveClass)}>
{content}
</button>
)}
</li>
);
}Props
Generated from the component's TypeScript types. Standard DOM and React Aria props are omitted.
Breadcrumb
| Prop | Type | Default | Description |
|---|---|---|---|
| aria-label | string | Breadcrumb | — |
| className | string | — | — |
BreadcrumbItem
| Prop | Type | Default | Description |
|---|---|---|---|
| className | string | — | — |
| current | boolean | false | Marks the active page: not interactive, darker text. |
| href | string | — | — |
| icon | IconComponent | — | Optional leading icon (16px), inherits the item color. |
| onClick | () => void | — | — |