Component
Breadcrumbs With Icons
Breadcrumb levels with optional meaningful leading icons — passed as ReactNode, rendered aria-hidden, and never required on every item.
- Component
- React
- TSX
- Tailwind CSS
- MIT
What it does
Capabilities
- Semantic nav landmark
- Ordered list
- Aria-current="page"
- Optional per-level icons
- Nav landmark with aria-label
- Ordered list structure
- Real anchor links
- Aria-hidden separators
- Icon aria-hidden
Preview
Live component
9:41 100%
devsnips.site/library/react/components/breadcrumbs/breadcrumbs-with-icons/ fluid · no fixed width 100%
Install
Add to your project
npx devsnips add React/Components/Breadcrumbs/breadcrumbs-with-icons React/Components/Breadcrumbs/breadcrumbs-with-icons import { createContext, useContext } from "react";
import type {
AnchorHTMLAttributes,
HTMLAttributes,
LiHTMLAttributes,
ReactNode,
} from "react";
/**
* DevSnips React Breadcrumbs — With Icons.
*
* Any level may carry a meaningful leading icon via the `icon` prop on
* `<BreadcrumbLink>` / `<BreadcrumbCurrent>`. Icons are optional ReactNode
* values rendered aria-hidden — the visible label always carries the
* accessible name. No icon library is required; pass any SVG node.
*/
function cx(...parts: Array<string | false | null | undefined>): string {
return parts.filter(Boolean).join(" ");
}
const LIST_CLASSES =
"m-0 flex max-w-full list-none flex-wrap items-center gap-x-1 gap-y-0.5 p-0 text-sm leading-5";
const ITEM_CLASSES = "inline-flex min-w-0 items-center gap-1.5";
const LINK_CLASSES =
"inline-flex min-w-0 items-center gap-1.5 rounded-[var(--ds-radius-xs)] text-[var(--ds-color-muted-foreground)] underline-offset-4 transition-colors duration-150 ease-out hover:text-[var(--ds-color-foreground)] hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--ds-color-focus-ring)] motion-reduce:transition-none";
const CURRENT_CLASSES =
"inline-flex min-w-0 items-center gap-1.5 font-medium text-[var(--ds-color-foreground)]";
const SEPARATOR_CLASSES =
"inline-flex shrink-0 select-none items-center justify-center text-[var(--ds-color-muted-foreground)] [&_svg]:size-3.5";
const ICON_CLASSES = "inline-flex shrink-0 text-[14px] [&_svg]:size-3.5";
const DEFAULT_SEPARATOR = (
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={1.75}
strokeLinecap="round"
strokeLinejoin="round"
aria-hidden="true"
focusable="false"
>
<path d="m9 6 6 6-6 6" />
</svg>
);
interface BreadcrumbsContextValue {
separator: ReactNode;
}
const BreadcrumbsContext = createContext<BreadcrumbsContextValue | null>(null);
function useBreadcrumbs(component: string): BreadcrumbsContextValue {
const context = useContext(BreadcrumbsContext);
if (!context) {
throw new Error(`<${component}> must be rendered inside <Breadcrumbs>.`);
}
return context;
}
export interface BreadcrumbsProps extends HTMLAttributes<HTMLElement> {
/** Accessible label for the navigation landmark. */
label?: string;
/** Default separator content used by every `<BreadcrumbSeparator>` without children. */
separator?: ReactNode;
className?: string;
children?: ReactNode;
}
export function Breadcrumbs({
label = "Breadcrumb",
separator,
className,
children,
...rest
}: BreadcrumbsProps) {
return (
<BreadcrumbsContext.Provider value={{ separator: separator ?? DEFAULT_SEPARATOR }}>
<nav aria-label={label} className={className} {...rest}>
{children}
</nav>
</BreadcrumbsContext.Provider>
);
}
export interface BreadcrumbListProps extends HTMLAttributes<HTMLOListElement> {
className?: string;
children?: ReactNode;
}
export function BreadcrumbList({ className, children, ...rest }: BreadcrumbListProps) {
return (
<ol className={cx(LIST_CLASSES, className)} {...rest}>
{children}
</ol>
);
}
export interface BreadcrumbItemProps extends LiHTMLAttributes<HTMLLIElement> {
className?: string;
children?: ReactNode;
}
export function BreadcrumbItem({ className, children, ...rest }: BreadcrumbItemProps) {
return (
<li className={cx(ITEM_CLASSES, className)} {...rest}>
{children}
</li>
);
}
export interface BreadcrumbLinkProps extends AnchorHTMLAttributes<HTMLAnchorElement> {
/** Destination URL — rendered as a real anchor with normal browser navigation. */
href: string;
/** Meaningful leading icon (rendered aria-hidden). */
icon?: ReactNode;
className?: string;
children?: ReactNode;
}
export function BreadcrumbLink({ href, icon, className, children, ...rest }: BreadcrumbLinkProps) {
return (
<a href={href} className={cx(LINK_CLASSES, className)} {...rest}>
{icon ? (
<span aria-hidden="true" className={ICON_CLASSES}>
{icon}
</span>
) : null}
<span className="min-w-0">{children}</span>
</a>
);
}
export interface BreadcrumbCurrentProps extends HTMLAttributes<HTMLSpanElement> {
/** Meaningful leading icon (rendered aria-hidden). */
icon?: ReactNode;
className?: string;
children?: ReactNode;
}
export function BreadcrumbCurrent({ icon, className, children, ...rest }: BreadcrumbCurrentProps) {
return (
<span aria-current="page" className={cx(CURRENT_CLASSES, className)} {...rest}>
{icon ? (
<span aria-hidden="true" className={ICON_CLASSES}>
{icon}
</span>
) : null}
<span className="min-w-0">{children}</span>
</span>
);
}
export interface BreadcrumbSeparatorProps extends LiHTMLAttributes<HTMLLIElement> {
className?: string;
children?: ReactNode;
}
export function BreadcrumbSeparator({ className, children, ...rest }: BreadcrumbSeparatorProps) {
const context = useBreadcrumbs("BreadcrumbSeparator");
return (
<li
role="presentation"
aria-hidden="true"
className={cx(SEPARATOR_CLASSES, className)}
{...rest}
>
{children ?? context.separator}
</li>
);
}
export default Breadcrumbs; /* DevSnips React — JavaScript parity build.
* Same API, behavior, and classes as code.tsx; TypeScript types removed.
* Regenerated from code.tsx — edit code.tsx and re-run the generator.
*/
import { createContext, useContext } from "react";
function cx(...parts) {
return parts.filter(Boolean).join(" ");
}
const LIST_CLASSES = "m-0 flex max-w-full list-none flex-wrap items-center gap-x-1 gap-y-0.5 p-0 text-sm leading-5";
const ITEM_CLASSES = "inline-flex min-w-0 items-center gap-1.5";
const LINK_CLASSES = "inline-flex min-w-0 items-center gap-1.5 rounded-[var(--ds-radius-xs)] text-[var(--ds-color-muted-foreground)] underline-offset-4 transition-colors duration-150 ease-out hover:text-[var(--ds-color-foreground)] hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--ds-color-focus-ring)] motion-reduce:transition-none";
const CURRENT_CLASSES = "inline-flex min-w-0 items-center gap-1.5 font-medium text-[var(--ds-color-foreground)]";
const SEPARATOR_CLASSES = "inline-flex shrink-0 select-none items-center justify-center text-[var(--ds-color-muted-foreground)] [&_svg]:size-3.5";
const ICON_CLASSES = "inline-flex shrink-0 text-[14px] [&_svg]:size-3.5";
const DEFAULT_SEPARATOR = <svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={1.75}
strokeLinecap="round"
strokeLinejoin="round"
aria-hidden="true"
focusable="false"
>
<path d="m9 6 6 6-6 6" />
</svg>;
const BreadcrumbsContext = createContext(null);
function useBreadcrumbs(component) {
const context = useContext(BreadcrumbsContext);
if (!context) {
throw new Error(`<${component}> must be rendered inside <Breadcrumbs>.`);
}
return context;
}
function Breadcrumbs({
label = "Breadcrumb",
separator,
className,
children,
...rest
}) {
return <BreadcrumbsContext.Provider value={{ separator: separator ?? DEFAULT_SEPARATOR }}>
<nav aria-label={label} className={className} {...rest}>
{children}
</nav>
</BreadcrumbsContext.Provider>;
}
function BreadcrumbList({ className, children, ...rest }) {
return <ol className={cx(LIST_CLASSES, className)} {...rest}>
{children}
</ol>;
}
function BreadcrumbItem({ className, children, ...rest }) {
return <li className={cx(ITEM_CLASSES, className)} {...rest}>
{children}
</li>;
}
function BreadcrumbLink({ href, icon, className, children, ...rest }) {
return <a href={href} className={cx(LINK_CLASSES, className)} {...rest}>
{icon ? <span aria-hidden="true" className={ICON_CLASSES}>
{icon}
</span> : null}
<span className="min-w-0">{children}</span>
</a>;
}
function BreadcrumbCurrent({ icon, className, children, ...rest }) {
return <span aria-current="page" className={cx(CURRENT_CLASSES, className)} {...rest}>
{icon ? <span aria-hidden="true" className={ICON_CLASSES}>
{icon}
</span> : null}
<span className="min-w-0">{children}</span>
</span>;
}
function BreadcrumbSeparator({ className, children, ...rest }) {
const context = useBreadcrumbs("BreadcrumbSeparator");
return <li
role="presentation"
aria-hidden="true"
className={cx(SEPARATOR_CLASSES, className)}
{...rest}
>
{children ?? context.separator}
</li>;
}
export { Breadcrumbs, BreadcrumbList, BreadcrumbItem, BreadcrumbLink, BreadcrumbCurrent, BreadcrumbSeparator };
export default Breadcrumbs; # Breadcrumbs With Icons
Breadcrumb levels with optional meaningful leading icons — passed as ReactNode, rendered aria-hidden, and never required on every item.
## Usage
```tsx
import Breadcrumbs, {
BreadcrumbList,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbCurrent,
BreadcrumbSeparator,
} from "./breadcrumbs-with-icons";
<Breadcrumbs>
<BreadcrumbList>
<BreadcrumbItem>
<BreadcrumbLink href="/" icon={<HomeIcon />}>Home</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbLink href="/documentation" icon={<DocsIcon />}>Documentation</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
{/* Icons are optional — this level carries none. */}
<BreadcrumbLink href="/documentation/components">Components</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbCurrent icon={<GridIcon />}>Buttons</BreadcrumbCurrent>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumbs>
```
## JavaScript
A `code.jsx` build is provided for projects that ship plain JSX. It exposes the same API and behavior as `code.tsx` — only the TypeScript types are removed.
```jsx
import Breadcrumbs, {
BreadcrumbList,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbCurrent,
BreadcrumbSeparator,
} from "./breadcrumbs-with-icons";
<Breadcrumbs>
<BreadcrumbList>
<BreadcrumbItem>
<BreadcrumbLink href="/" icon={<HomeIcon />}>Home</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbLink href="/documentation" icon={<DocsIcon />}>Documentation</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
{/* Icons are optional — this level carries none. */}
<BreadcrumbLink href="/documentation/components">Components</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbCurrent icon={<GridIcon />}>Buttons</BreadcrumbCurrent>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumbs>
```
## Props
### `<Breadcrumbs>`
| Name | Type | Default | Description |
|---|---|---|---|
| `label` | `string` | `"Breadcrumb"` | Accessible label for the `<nav>` landmark. |
| `separator` | `ReactNode` | chevron icon | Default separator content for every `<BreadcrumbSeparator>` without children. |
| `className` | `string` | — | Extra classes on the `<nav>`. |
| `children` | `ReactNode` | — | `BreadcrumbList` composition. |
### `<BreadcrumbList>`
| Name | Type | Default | Description |
|---|---|---|---|
| `className` | `string` | — | Extra classes on the `<ol>`. |
| `children` | `ReactNode` | — | `BreadcrumbItem` + `BreadcrumbSeparator` elements. |
### `<BreadcrumbItem>`
| Name | Type | Default | Description |
|---|---|---|---|
| `className` | `string` | — | Extra classes on the `<li>`. |
| `children` | `ReactNode` | — | Usually one `BreadcrumbLink` or `BreadcrumbCurrent`. |
### `<BreadcrumbLink>`
| Name | Type | Default | Description |
|---|---|---|---|
| `href` | `string` (required) | — | Destination URL — rendered as a real anchor with normal browser navigation. |
| `icon` | `ReactNode` | — | Meaningful leading icon (rendered `aria-hidden`). |
| `className` | `string` | — | Extra classes on the anchor. |
| `children` | `ReactNode` | — | Visible label. |
All native anchor attributes (`target`, `rel`, `aria-label`, `title`, …) are forwarded.
### `<BreadcrumbCurrent>`
| Name | Type | Default | Description |
|---|---|---|---|
| `icon` | `ReactNode` | — | Meaningful leading icon (rendered `aria-hidden`). |
| `className` | `string` | — | Extra classes on the span. |
| `children` | `ReactNode` | — | Visible label (rendered with `aria-current="page"`). |
### `<BreadcrumbSeparator>`
| Name | Type | Default | Description |
|---|---|---|---|
| `className` | `string` | — | Extra classes on the list item. |
| `children` | `ReactNode` | context `separator` | Custom separator content for this position only. |
## Composition
Breadcrumbs is a compound component. Six primitives compose the pattern:
```tsx
<Breadcrumbs>
<BreadcrumbList>
<BreadcrumbItem>
<BreadcrumbLink href="/">Home</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbLink href="/documentation">Documentation</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbCurrent>Buttons</BreadcrumbCurrent>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumbs>
```
- `Breadcrumbs` — the root `<nav aria-label="Breadcrumb">` landmark. Provides the default `separator` to every separator through context.
- `BreadcrumbList` — the ordered list (`<ol>`). Wraps onto multiple lines instead of scrolling horizontally.
- `BreadcrumbItem` — one level of the trail (`<li>`).
- `BreadcrumbLink` — a real `<a href>` for navigable levels: normal browser navigation, optional leading `icon`.
- `BreadcrumbCurrent` — the current page: plain text with `aria-current="page"`, never a link.
- `BreadcrumbSeparator` — decorative structure between levels (`role="presentation"`, `aria-hidden`); renders its own `children`, else the `separator` given to `<Breadcrumbs>`, else the default chevron.
No new primitives — `BreadcrumbLink` and `BreadcrumbCurrent` both accept an optional `icon` ReactNode. Icons communicate the level's meaning (a book for documentation, a grid for a component library); they are not decoration, and no level is required to have one.
## Keyboard Interaction
| Key | Behavior |
|---|---|
| `Tab` / `Shift+Tab` | Move focus through the links |
| `Enter` | Follow the focused link |
## Accessibility
The structure follows the W3C Breadcrumb pattern: a `<nav aria-label="Breadcrumb">` landmark containing an ordered list (`<ol>`) whose last item is the current page marked with `aria-current="page"`.
- Every navigable level is a real `<a href>` — normal browser navigation, no click handlers faking links.
- The current page is plain text, not a link; assistive technology announces it as the current page.
- Separators are `aria-hidden` `role="presentation"` list items — never announced, never focusable.
- Icons are decorative (`aria-hidden`); the visible label always carries the accessible name.
Every icon is rendered `aria-hidden` — the visible label carries the accessible name, so screen readers never hear a redundant or missing glyph description. No icon library dependency is introduced: pass any SVG ReactNode.
## States
- **Link** — muted foreground; hover shifts to the foreground color with an underline.
- **Current page** — foreground color at medium weight; not interactive.
- **Separator** — muted decorative glyph, hidden from assistive technology.
- **Focus-visible** — `--ds-color-focus-ring` outline on links and menu triggers in both themes.
## Responsive Behavior
The list uses `flex-wrap` with a `min-w-0` flexible item per level, so long trails wrap onto multiple lines instead of forcing page-level horizontal scrolling. From 375px up, prefer intentional reduction over squeezing: collapse middle levels with `breadcrumbs-collapsed` and cap long labels with `breadcrumbs-max-width`.
## Styling
Built with React, Tailwind CSS, and DevSnips design tokens. The component consumes the `--ds-*` semantic tokens via arbitrary values (for example `text-[var(--ds-color-muted-foreground)]`). Define the tokens once in your theme — no component-specific CSS file is required.
## Design Tokens
See [React/DESIGN_TOKENS.md](../../../DESIGN_TOKENS.md) for the authoritative token specification. This breadcrumb variant uses the semantic color, radius, typography, and motion tokens.
## Notes
Icons inherit the 14px label size (`[&_svg]:size-3.5`) and the link's currentColor, so they track hover and theme changes with the text. 173 lines UTF-8 · LF · Spaces: 2
DevSnips AI ready Claude · Cursor · Windsurf
Agent skill & Model Context Protocol
Pass this component straight into your AI workflow. DevSnips exposes each resource as a structured, addressable registry entry, so your agent installs the real files instead of approximating them.
devsnips://components/breadcrumbs/breadcrumbs-with-icons I want to use this DevSnips resource in production: Page: https://devsnips.site/library/react/components/breadcrumbs/breadcrumbs-with-icons/ Install: npx devsnips add React/Components/Breadcrumbs/breadcrumbs-with-icons This is the Breadcrumbs With Icons — Breadcrumb levels with optional meaningful leading icons — passed as ReactNode, rendered aria-hidden, and never required on every item. Use this DevSnips resource as the starting point for my project. Adapt it to my project's stack and conventions, fix any issues, and make it production-ready while preserving its intended design and behavior.
Paste into ChatGPT, Claude or your agent — it already carries the page URL and the exact install path.
Resource URI
devsnips://components/breadcrumbs/breadcrumbs-with-icons