Breadcrumb
Breadcrumb displays a trail of links tracing the current page's position within a site's hierarchy, from the root down to where someone actually is. The final step always stays a plain label rather than a link, since navigating to the page someone's already on would be redundant.
Dependencies
When to use
Add a breadcrumb once someone can be more than one level deep in your site, such as a product inside a category or a document inside a folder, so they can always see where they landed and step back without hitting the browser's back button. Skip it on flat, top-level pages where there's nowhere meaningful to trace back to, and never let it be the only way to navigate. It should complement a real primary navigation, not replace one.
Install & usage
Pick a framework in the toolbar above and these snippets adapt.
npm install @eunomia/elements
import "@eunomia/elements/breadcrumb.js";
<eun-breadcrumb aria-label="Breadcrumb">
<eun-breadcrumb-item href="/">Home</eun-breadcrumb-item>
<eun-breadcrumb-item href="/catalogue">Catalogue</eun-breadcrumb-item>
<eun-breadcrumb-item current>Kitchen mixer tap</eun-breadcrumb-item>
</eun-breadcrumb>
Importing the file registers <eun-breadcrumb> and <eun-breadcrumb-item>
as custom elements, with no further setup needed. It works with any
framework, or none, since they're standard web components. One thing to
remember is current on the last step, since eun-breadcrumb-item has no
way to know on its own that it's the last of its siblings, and never
infers that state for you (see the Accessibility tab).
npm install @eunomia/elements
<script type="module">
import "@eunomia/elements/breadcrumb.js";
</script>
<eun-breadcrumb aria-label="Breadcrumb">
<eun-breadcrumb-item href="/">Home</eun-breadcrumb-item>
<eun-breadcrumb-item href="/catalogue">Catalogue</eun-breadcrumb-item>
<eun-breadcrumb-item current>Kitchen mixer tap</eun-breadcrumb-item>
</eun-breadcrumb>
npm install @eunomia/elements
import "@eunomia/elements/breadcrumb.js";
function ProductBreadcrumb() {
return (
<eun-breadcrumb aria-label="Breadcrumb">
<eun-breadcrumb-item href="/">Home</eun-breadcrumb-item>
<eun-breadcrumb-item href="/catalogue">Catalogue</eun-breadcrumb-item>
<eun-breadcrumb-item current>Kitchen mixer tap</eun-breadcrumb-item>
</eun-breadcrumb>
);
}
npm install @eunomia/elements
"use client";
import "@eunomia/elements/breadcrumb.js";
export function ProductBreadcrumb() {
return (
<eun-breadcrumb aria-label="Breadcrumb">
<eun-breadcrumb-item href="/">Home</eun-breadcrumb-item>
<eun-breadcrumb-item href="/catalogue">Catalogue</eun-breadcrumb-item>
<eun-breadcrumb-item current>Kitchen mixer tap</eun-breadcrumb-item>
</eun-breadcrumb>
);
}
npm install @eunomia/elements
<script setup>
import "@eunomia/elements/breadcrumb.js";
</script>
<template>
<eun-breadcrumb aria-label="Breadcrumb">
<eun-breadcrumb-item href="/">Home</eun-breadcrumb-item>
<eun-breadcrumb-item href="/catalogue">Catalogue</eun-breadcrumb-item>
<eun-breadcrumb-item current>Kitchen mixer tap</eun-breadcrumb-item>
</eun-breadcrumb>
</template>
npm install @eunomia/elements
import { CUSTOM_ELEMENTS_SCHEMA, Component } from "@angular/core";
import "@eunomia/elements/breadcrumb.js";
@Component({
selector: "app-product-breadcrumb",
template: `
<eun-breadcrumb aria-label="Breadcrumb">
<eun-breadcrumb-item href="/">Home</eun-breadcrumb-item>
<eun-breadcrumb-item href="/catalogue">Catalogue</eun-breadcrumb-item>
<eun-breadcrumb-item current>Kitchen mixer tap</eun-breadcrumb-item>
</eun-breadcrumb>
`,
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class ProductBreadcrumbComponent {}
Guidance
- Use
eun-breadcrumbon any page nested more than one level deep, so people can see where they are and jump back to an ancestor page in one click - Always mark the trail's actual last step with
current, sinceeun-breadcrumb-itemnever infers it from position (see the Accessibility tab) - Keep labels short, since long ones truncate with an ellipsis past
--breadcrumb-item-max-width(see the Examples tab) - Pair the
iconprop with visible text on aeun-breadcrumb-itemstep, since an icon alone has no accessible name (see the Accessibility tab)
- Using it on top-level pages, since a one- or two-step trail rarely earns its place
- Setting
hrefon the step markedcurrentand expecting it to still link.currentalways wins, rendering unlinked text regardless, since linking to the page someone's already on is a confusing, redundant control - Using it as the page's only means of navigating back. Pair it with a proper primary navigation, not instead of one
Live testing
Properties
eun-breadcrumb mostly just needs aria-label (defaults to "Breadcrumb")
and its eun-breadcrumb-item children. items below is the data-driven
alternative (see "Data-driven trail" in the Examples tab).
Breadcrumb <eun-breadcrumb>
Attributes
| Name | Type | Default | Description |
|---|---|---|---|
| aria-label | string | 'Breadcrumb' | The accessible label for the navigation landmark |
| items | Array<EunomiaBreadcrumbItemData> | [] | The trail's steps, in order, from the root to the current page. Each entry may set a label, an href, and an optional leading icon |
Import the exact TypeScript type behind any property above, see
Slots
| Name | Description |
|---|---|
| (default) | Breadcrumb item elements, used instead of or alongside items |
CSS custom properties
| Name | Description |
|---|---|
| --breadcrumb-link-color | Sets the color of linked steps |
| --breadcrumb-link-color-hover | Sets the color of linked steps on hover or focus |
| --breadcrumb-current-color | Sets the color of the current page and unlinked steps |
| --breadcrumb-focus-outline-color | Sets the color of a linked step's focus outline |
| --breadcrumb-separator | Sets the separator between steps, as a quoted CSS string, such as '>' |
| --breadcrumb-separator-color | Sets the color of the separator |
| --breadcrumb-separator-gap | Sets the gap around the separator |
| --breadcrumb-item-max-width | Sets the maximum width of each step, beyond which its label truncates with an ellipsis |
| --breadcrumb-gap | Sets the row gap, used when the trail wraps onto multiple lines |
| --breadcrumb-transition-duration | Sets the duration of the link hover color transition |
Into an item
Breadcrumb item <eun-breadcrumb-item>
Attributes
| Name | Type | Default | Description |
|---|---|---|---|
| href | string | — | The URL this step links to. Renders as plain, unlinked text when unset, such as a category that groups pages but isn't itself a page |
| current | boolean | false | Marks this as the trail's current, unlinked page, regardless of href |
| icon | EunomiaIconName | — | An optional leading icon shown before the slotted label. Purely decorative, so it never substitutes for the label's own visible text |
Import the exact TypeScript type behind any property above, see
Slots
| Name | Description |
|---|---|
| (default) | The step's label content |
| icon | Replaces the leading icon entirely with any content, not just an icon element. Falls back to icon when empty |
Basic
<eun-breadcrumb aria-label="Breadcrumb">
<eun-breadcrumb-item href="/">Home</eun-breadcrumb-item>
<eun-breadcrumb-item href="/catalogue">Catalogue</eun-breadcrumb-item>
<eun-breadcrumb-item current>Kitchen mixer tap</eun-breadcrumb-item>
</eun-breadcrumb>
Two steps
<eun-breadcrumb aria-label="Breadcrumb">
<eun-breadcrumb-item href="/">Home</eun-breadcrumb-item>
<eun-breadcrumb-item current>Catalogue</eun-breadcrumb-item>
</eun-breadcrumb>
Unlinked intermediate step
A step with neither href nor current renders as plain text instead of
a link, useful for a category that groups pages without being a page (or
the current page) itself.
<eun-breadcrumb aria-label="Breadcrumb">
<eun-breadcrumb-item href="/">Home</eun-breadcrumb-item>
<eun-breadcrumb-item>Faucets</eun-breadcrumb-item>
<eun-breadcrumb-item current>Kitchen mixer tap</eun-breadcrumb-item>
</eun-breadcrumb>
Long labels truncate
Each step's label truncates with an ellipsis past
--breadcrumb-item-max-width (240px by default), so one long label can't
push the trail off-screen.
Custom separator
--breadcrumb-separator takes any quoted CSS string.
<eun-breadcrumb aria-label="Breadcrumb" style="--breadcrumb-separator: '>';">
...
</eun-breadcrumb>
With an icon
The icon prop renders a leading icon before the label, using eun-icon
under the hood. Its module must still be imported alongside breadcrumb
whenever a step sets it, as shown below (see the dependency badge above).
An icon should always be paired with visible text, since an icon-only step
has no accessible name (see the Accessibility tab).
import "@eunomia/elements/icon.js";
<eun-breadcrumb-item href="/" icon="home">Home</eun-breadcrumb-item>
Custom icon markup
The default slot still accepts any content, so slotting an icon by hand
works too, alongside (or instead of) the icon prop above. Reach for it
when a step needs something the prop can't express, such as a different
icon size or an entirely different icon component.
<eun-breadcrumb-item href="/">
<eun-icon name="home" size="18"></eun-icon>
Home
</eun-breadcrumb-item>
It needs the same import as the icon prop above, since eun-icon must be registered for either approach to render anything.
Custom icon, via the icon slot
Need something other than a eun-icon, or a different icon library
entirely, without mixing it into the label text itself? Slot your own
content into the dedicated icon slot instead — it replaces the
prop-driven icon entirely and is kept visually separate from the label.
<eun-breadcrumb-item href="/">
<svg
slot="icon"
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2Z"></path>
</svg>
Home
</eun-breadcrumb-item>
Custom
Override the --breadcrumb-* CSS variables, listed in full in the API tab.
<eun-breadcrumb class="custom-breadcrumb" aria-label="Breadcrumb">
...
</eun-breadcrumb>
.custom-breadcrumb {
--breadcrumb-link-color: #be185d;
--breadcrumb-link-color-hover: #9d174d;
--breadcrumb-separator: ">";
}
Data-driven trail
An alternative to writing out eun-breadcrumb-item children by hand. Set
items to an array and eun-breadcrumb renders the whole trail itself,
including which step counts as current. Each entry can set a label, an
href, and an optional leading icon, so it's well suited to a trail
built from route data (e.g. inside a router-integrated layout). Reach for
slotted eun-breadcrumb-items instead once a step needs richer content
than that.
Unlike eun-breadcrumb-item's own explicit current, the last entry
in items is always rendered as the current, unlinked page, regardless of
whether it has an href. eun-breadcrumb knows its own array's length, so
it can infer that for you here, while a standalone eun-breadcrumb-item
has no equivalent way to know it's the last of its siblings, which is
exactly why it needs current set explicitly instead (see the
Accessibility tab). An intermediate entry without an href still renders
as plain, unlinked text either way.
<eun-breadcrumb aria-label="Breadcrumb"></eun-breadcrumb>
document.querySelector("eun-breadcrumb").items = [
{ label: "Home", href: "/", icon: "home" },
{ label: "Catalogue", href: "/catalogue" },
{ label: "Kitchen mixer tap" },
];
Keyboard interactions
eun-breadcrumb adds no custom keyboard handling of its own. Every step is
a native <a> (or plain text), so standard link navigation already
applies:
| Key | Action |
|---|---|
Tab / Shift+Tab |
Moves focus between linked steps, in trail order |
Enter |
Follows the focused step's link |
Aria attributes and rules
- Rendered as
<nav aria-label="Breadcrumb">wrapping an<ol role="list">, the standard breadcrumb pattern recommended by the WAI-ARIA APG. Overridearia-labelfor other languages/contexts. role="list"is set explicitly despite<ol>having it implicitly: Safari drops the implicit role oncelist-style: noneis applied (needed here for the visual flex layout), which would otherwise silently skip the list for VoiceOver users.- The current step is a
<span aria-current="page">, never a link, even if it happens to carry anhref, since linking to the page someone's already on adds a confusing, redundant control. - Steps without an
hrefrender as plain text (not a disabled-looking link), since they aren't pages of their own.
current is explicit, on purpose
A standalone eun-breadcrumb-item has no way to know it's the last of its
siblings, unlike eun-breadcrumb's own items array, where the array's
length already tells it which entry is last. Omitting href alone isn't
equivalent to current. Without it, a eun-breadcrumb-item with no
href renders as plain unlinked text but without aria-current="page",
indistinguishable, to a screen reader, from an unlinked intermediate
category like "Faucets" in the example above. Set current explicitly on
whichever step is genuinely the trail's current page.
Label accessibility
A step's accessible name comes entirely from its content, whether plain
text or whatever's slotted into a eun-breadcrumb-item. An icon-only
step has no accessible name, since the icon prop renders eun-icon
with aria-hidden="true" by design (see the Icon component), and
content-based naming has nothing else to fall back to. Always pair an icon
with visible text (see "With an icon" above), or set aria-label directly
on the eun-breadcrumb-item if the text truly can't be visible. It's
forwarded straight to its inner link/text, not left stranded on the outer
element alone.
Long labels
Labels truncate visually past --breadcrumb-item-max-width, but the full
text stays in the DOM. Screen readers always read the complete label,
regardless of how much of it is visually clipped.
Reference links