Breadcrumbs

A hierarchy of links to the current page in an application.

Usage

BreadcrumbsExample.tsx
  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
function Example() {
const items = [
{id: 1, label: 'Home', href: '/'},
{id: 2, label: 'Navigation', href: '/components/navigation'},
{id: 3, label: 'Breadcrumbs', href: '/components/navigation/breadcrumbs'},
]
return <Breadcrumbs items={items} />
}

Here's the <Breadcrumbs /> component in action.

Features

  • Implemented as an ordered list of links.
  • The last link is automatically marked as the current page using aria-current.
  • Hover, press, and keyboard focus states are provided for easy styling.

Source

breadcrumbs.tsx
  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
  11. 11
  12. 12
  13. 13
  14. 14
  15. 15
  16. 16
  17. 17
  18. 18
  19. 19
  20. 20
  21. 21
  22. 22
  23. 23
  24. 24
  25. 25
  26. 26
  27. 27
  28. 28
  29. 29
  30. 30
  31. 31
  32. 32
import { forwardRef, type ElementRef } from 'react'
import { Breadcrumbs as ReactAriaBreadcrumbs, Breadcrumb, Link, type BreadcrumbsProps } from 'react-aria-components'
import { Icon } from '#app/old_components/ui/icon.tsx'
import { cn } from '#app/utils/tailwind-merge.ts'
type Breadcrumb = {
href: string
label: string
}
type BreadcrumbItem = Breadcrumb & {
id: string | number
}
const Breadcrumbs = forwardRef<ElementRef<typeof ReactAriaBreadcrumbs>, BreadcrumbsProps<BreadcrumbItem>>(({ className, ...props }, ref) => {
return (
<ReactAriaBreadcrumbs ref={ref} className={cn('breadcrumbs', className)} {...props}>
{({ id, href, label }: BreadcrumbItem) => (
<Breadcrumb key={id} className="breadcrumb">
<Link className="peer" href={href}>
{label}
</Link>
<Icon name="chevron-right" aria-hidden="true" className="icon peer-data-[current]:hidden" />
</Breadcrumb>
)}
</ReactAriaBreadcrumbs>
)
})
Breadcrumbs.displayName = 'Breadcrumbs'
export { Breadcrumbs, type Breadcrumb, type BreadcrumbItem }

Styling

tailwind.css
  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
  11. 11
  12. 12
  13. 13
  14. 14
  15. 15
  16. 16
  17. 17
  18. 18
/* ### Navigation ### */
@layer components {
.breadcrumbs {
@apply flex flex-wrap;
& .breadcrumb {
@apply flex items-center;
& a {
@apply p-1 text-foreground opacity-50 data-[disabled]:opacity-100;
}
& .icon {
@apply h-4 w-4 opacity-50;
}
}
}
}

Notes

The <Breadcrumbs /> component receives an array of items to render as links. But it's up to you to figure out how to generate the items array based on your framework.

I suggest you create an additional component that encapsulates this logic.

Here's how you'd do it in Remix:

RemixBreadcrumbs.tsx
  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
  11. 11
  12. 12
  13. 13
  14. 14
  15. 15
  16. 16
  17. 17
  18. 18
  19. 19
import { useMatches } from '@remix-run/react'
import { type ElementRef, forwardRef } from 'react'
import { type BreadcrumbsProps } from 'react-aria-components'
import { type BreadcrumbItem, Breadcrumbs } from './breadcrumbs.tsx'
const RemixBreadcrumbs = forwardRef<ElementRef<typeof Breadcrumbs>, BreadcrumbsProps<BreadcrumbItem>>(({ ...props }, ref) => {
const matches = useMatches()
const items: BreadcrumbItem[] = matches
.filter(match => match.handle && (match.handle as { breadcrumb: BreadcrumbItem }).breadcrumb)
.map(match => {
const { id } = match
const { href, label } = (match.handle as { breadcrumb: BreadcrumbItem }).breadcrumb
return { id, href, label }
})
return <Breadcrumbs ref={ref} items={items} {...props} />
})
RemixBreadcrumbs.displayName = 'RemixBreadcrumbs'
export { RemixBreadcrumbs }

Then, just place this <RemixBreadcrumbs /> component wherever you want breadcrumbs to show up in your app.

Remember that, in Remix, each route decides if and how it will contribute to the breadcrumbs using a handle export.

RemixBreadcrumbs.tsx
  1. 1
  2. 2
  3. 3
export const handle = {
breadcrumb: () => <Link to="/parent">Some Route</Link>,
}