Skip to content

Breadcrumb

Shows where the current page sits in the hierarchy and links back to each parent. It renders plain links and works with any router through asChild.

Installation

Add Breadcrumb to your project with the CK-UI CLI. It copies the source into your project, so the code is yours to change.

Terminal
npx ck-ui-cli@latest add breadcrumb
Adds
Breadcrumb.tsxto your CK-UI folder (src/components/ck-ui by default)
Installs
@radix-ui/react-slotreact-iconsif your project does not have them yet

First time with CK-UI in this project? Run npx ck-ui-cli@latest init once before adding components. The installation guide explains what it sets up.

Manual installationInstall the packages and copy the files yourself.

Install the following dependencies

Run these commands in your terminal to install the required packages

Terminal
npm install @radix-ui/react-slot react-icons

Create or update your Breadcrumb component

Copy this code to create your Breadcrumb component

/components/ui/Breadcrumb.tsx
import * as React from "react";
import { Slot } from "@radix-ui/react-slot";
import { RiArrowRightSLine, RiMoreLine } from "react-icons/ri";
import { cn } from "@/lib/utils";

/*
 * <nav aria-label="Breadcrumb"><ol>…</ol></nav>, with the current page marked aria-current="page".
 * Separators are hidden from assistive tech. Links render plain <a> elements; use `asChild` for a
 * router link (e.g. Next.js Link). Server-compatible, no router dependency.
 */

const Root = React.forwardRef<HTMLElement, React.HTMLAttributes<HTMLElement>>(({ "aria-label": ariaLabel = "Breadcrumb", ...props }, ref) => (
    <nav ref={ref} aria-label={ariaLabel} {...props} />
));
Root.displayName = "Breadcrumb.Root";

/** The ordered list. Wraps onto several lines when there is not enough room. */
const List = React.forwardRef<HTMLOListElement, React.OlHTMLAttributes<HTMLOListElement>>(({ className, ...props }, ref) => (
    <ol ref={ref} className={cn("flex flex-wrap items-center gap-1.5 break-words size-p-sm text-muted-foreground sm:gap-2", className)} {...props} />
));
List.displayName = "Breadcrumb.List";

const Item = React.forwardRef<HTMLLIElement, React.LiHTMLAttributes<HTMLLIElement>>(({ className, ...props }, ref) => (
    <li ref={ref} className={cn("inline-flex min-w-0 max-w-full items-center gap-1.5", className)} {...props} />
));
Item.displayName = "Breadcrumb.Item";

export interface BreadcrumbLinkProps extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
    /** Render the single child (e.g. a Next.js <Link>) with the link styles. */
    asChild?: boolean;
}

const linkClasses = "inline-flex min-w-0 items-center gap-1.5 rounded-sm transition-colors duration-base ease-standard hover:text-foreground focus-ring [&>svg]:size-4 [&>svg]:shrink-0";

const Link = React.forwardRef<HTMLAnchorElement, BreadcrumbLinkProps>(({ asChild = false, className, ...props }, ref) => {
    const Comp = asChild ? Slot : "a";
    return <Comp ref={ref} className={cn(linkClasses, className)} {...props} />;
});
Link.displayName = "Breadcrumb.Link";

export interface BreadcrumbCurrentProps extends React.HTMLAttributes<HTMLSpanElement> {
    asChild?: boolean;
}

/** The current page: not a link, marked aria-current="page". */
const Current = React.forwardRef<HTMLSpanElement, BreadcrumbCurrentProps>(({ asChild = false, className, ...props }, ref) => {
    const Comp = asChild ? Slot : "span";
    return (
        <Comp
            ref={ref}
            aria-current="page"
            className={cn("inline-flex min-w-0 items-center gap-1.5 font-medium text-foreground [&>svg]:size-4 [&>svg]:shrink-0", className)}
            {...props}
        />
    );
});
Current.displayName = "Breadcrumb.Current";

/** Decorative separator between items (defaults to a chevron). */
const Separator = React.forwardRef<HTMLLIElement, React.LiHTMLAttributes<HTMLLIElement>>(({ className, children, ...props }, ref) => (
    <li ref={ref} role="presentation" aria-hidden="true" className={cn("flex items-center text-muted-foreground [&>svg]:size-4", className)} {...props}>
        {children ?? <RiArrowRightSLine />}
    </li>
));
Separator.displayName = "Breadcrumb.Separator";

export interface BreadcrumbEllipsisProps extends React.HTMLAttributes<HTMLSpanElement> {
    /** Text for screen readers (default "More pages"). */
    label?: string;
}

/** Stands in for collapsed items. Put it inside a button (e.g. a Dropdown trigger) to reveal them. */
const Ellipsis = React.forwardRef<HTMLSpanElement, BreadcrumbEllipsisProps>(({ label = "More pages", className, ...props }, ref) => (
    <span ref={ref} className={cn("flex size-6 items-center justify-center", className)} {...props}>
        <RiMoreLine aria-hidden="true" className="size-4" />
        <span className="sr-only">{label}</span>
    </span>
));
Ellipsis.displayName = "Breadcrumb.Ellipsis";

export { Root, List, Item, Link, Current, Separator, Ellipsis };

Test Your Component

  • 1. Add the component to a test page
  • 2. Check all props and variants work as expected
  • 3. Verify responsive behavior
  • 4. Test with different content/data
  • 5. Ensure accessibility features work correctly

Wrap your router's link with Breadcrumb.Link asChild, here Next.js Link in a nested docs trail.

With Icons

Icons go inside the link. An icon-only link needs visually hidden text. Pass children to Separator to change it.

Collapsed

For long trails, collapse the middle into Breadcrumb.Ellipsis inside a menu button, and truncate a long current page. The list also wraps rather than overflowing on narrow screens.

Accessibility

  • Rendered as <nav aria-label="Breadcrumb"> around an ordered list, so it is a navigation landmark and the number of levels is announced.
  • The current page uses Breadcrumb.Current: aria-current="page", not a link.
  • Separators are presentational list items with aria-hidden, so they are not read or counted.
  • Breadcrumb.Ellipsis carries "More pages" for screen readers; when it opens a menu, put it inside a button with its own label.
  • Truncated labels keep their full text as the accessible name.

API Reference

Server-compatible, no router dependency. Import with import * as Breadcrumb from "@/components/ui/Breadcrumb".

The nav landmark.

PropTypeDefaultDescription
aria-label"string""Breadcrumb"

Landmark name.

className"string""undefined"

Additional classes, merged last.

The ordered list. Wraps onto new lines when needed.

PropTypeDefaultDescription
className"string""undefined"

Additional classes, merged last.

A list item.

PropTypeDefaultDescription
className"string""undefined"

Additional classes, merged last.

A link to a parent page. Renders an <a>; other anchor attributes are passed through.

PropTypeDefaultDescription
href"string""undefined"

Destination.

asChild"boolean""false"

Render your router's link instead of <a>.

className"string""undefined"

Additional classes, merged last.

The current page, with aria-current="page".

PropTypeDefaultDescription
asChild"boolean""false"

Render the single child element instead, merging this part's props and behaviour onto it.

className"string""undefined"

Additional classes, merged last.

Decorative separator (a chevron by default).

PropTypeDefaultDescription
children"ReactNode""chevron icon"

Custom separator.

className"string""undefined"

Additional classes, merged last.

Stands in for collapsed items.

PropTypeDefaultDescription
label"string""More pages"

Screen-reader text.

className"string""undefined"

Additional classes, merged last.

Used in these blocks

Block families with at least one block built on this component. Each page previews its blocks and lists the components and packages they use.