Navigation
Breadcrumbs
Compact navigation trail built on react-aria-components. Intermediate crumbs are muted brand-teal links; the current page is white and semibold, with a chevron or slash separator.
navigationbreadcrumbstraillinks
Slash separator
Use separator="slash" for narrower trails or beside a page title.
Source
Copy this into your project. Resolve its dependencies from the registryDependencies in the component's API entry.
"use client"
import { createContext, use } from "react"
import type { BreadcrumbProps, BreadcrumbsProps, LinkProps } from "react-aria-components"
import { Breadcrumb, Breadcrumbs as BreadcrumbsPrimitive } from "react-aria-components"
import { Link } from "@/components/link"
import { cn } from "@/lib/utils"
/**
* Breadcrumbs — quebi design system
*
* Compact navigation trail. Intermediate crumbs render as brand-teal links
* dimmed to text-quebi-fg-muted; the current crumb is text-white and
* semibold. The separator glyph (chevron or slash) is subtle. Built on
* react-aria-components for the accessibility baseline.
*/
type BreadcrumbsContextProps = { separator?: "chevron" | "slash" | boolean }
const BreadcrumbsProvider = createContext<BreadcrumbsContextProps>({
separator: "chevron",
})
const Breadcrumbs = <T extends object>({
className,
...props
}: BreadcrumbsProps<T> & BreadcrumbsContextProps) => {
return (
<BreadcrumbsProvider value={{ separator: props.separator }}>
<BreadcrumbsPrimitive
{...props}
className={cn("flex items-center gap-2 font-sans text-sm", className)}
/>
</BreadcrumbsProvider>
)
}
interface BreadcrumbsItemProps extends BreadcrumbProps, BreadcrumbsContextProps {
href?: string
}
const BreadcrumbsItem = ({
href,
separator = true,
className,
...props
}: BreadcrumbsItemProps & Partial<Omit<LinkProps, "className">>) => {
const { separator: contextSeparator } = use(BreadcrumbsProvider)
separator = contextSeparator ?? separator
const separatorValue = separator === true ? "chevron" : separator
return (
<Breadcrumb
className={cn("flex items-center gap-2 text-quebi-fg-muted", className)}
data-slot="breadcrumb-item"
{...props}
>
{({ isCurrent }) => (
<>
<Link
className={cn(
"no-underline",
"has-data-[slot=icon]:inline-flex has-data-[slot=icon]:items-center has-data-[slot=icon]:gap-x-2",
"*:data-[slot=icon]:size-4",
isCurrent
? "cursor-default font-semibold text-white hover:text-white hover:no-underline"
: "font-normal text-quebi-fg-muted hover:text-white hover:no-underline *:data-[slot=icon]:text-quebi-fg-muted hover:*:data-[slot=icon]:text-white",
)}
href={href}
{...props}
/>
{!isCurrent && separator !== false && <Separator separator={separatorValue} />}
</>
)}
</Breadcrumb>
)
}
const ChevronRightIcon = () => (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
aria-hidden="true"
data-slot="icon"
>
<path
fillRule="evenodd"
d="M7.21 14.77a.75.75 0 0 1 .02-1.06L11.168 10 7.23 6.29a.75.75 0 1 1 1.04-1.08l4.5 4.25a.75.75 0 0 1 0 1.08l-4.5 4.25a.75.75 0 0 1-1.06-.02Z"
clipRule="evenodd"
/>
</svg>
)
const Separator = ({
separator = "chevron",
}: {
separator?: BreadcrumbsItemProps["separator"]
}) => {
return (
<span className="*:shrink-0 *:text-quebi-fg-subtle *:data-[slot=icon]:size-3.5">
{separator === "chevron" && <ChevronRightIcon />}
{separator === "slash" && <span className="text-quebi-fg-subtle">/</span>}
</span>
)
}
Breadcrumbs.Item = BreadcrumbsItem
export type { BreadcrumbsItemProps, BreadcrumbsProps }
export { Breadcrumbs, BreadcrumbsItem }