Breadcrumb
A navigational landmark that shows the user's current location in a hierarchy. Renders a semantic <nav> with an ordered list of bit-breadcrumb-item links, separated by a customizable separator glyph.
Features
- 📍 Semantic HTML: renders
<nav>→<ol>→<li>for proper landmark semantics - ✅ active prop: marks the current page — adds
aria-current="page"and disables the link - 🔗 Flexible hrefs: each item accepts an
hreffor standard navigation - 🎨 Custom separator: override the separator character per-instance or via CSS variable
- 🖼️ Icon slot: each item supports a leading
iconslot - ♿ ARIA:
aria-labelon<nav>,aria-current="page"on active item,aria-disabledon the active link
Source Code
View Source Code
import { define, prop, effect, html } from '@vielzeug/craftit';
import itemStyles from './breadcrumb-item.css?inline';
export type BitBreadcrumbProps = {
label?: string;
separator?: string;
};
export type BitBreadcrumbItemProps = {
active?: boolean;
href?: string;
separator?: string;
};
/**
* A single breadcrumb entry rendered inside `<bit-breadcrumb>`.
*
* @element bit-breadcrumb-item
*
* @attr {boolean} active - Marks this item as the current page (`aria-current="page"`)
* @attr {string} href - Link target for this breadcrumb item
* @attr {string} separator - Separator text shown before the item (except first item)
*
* @slot - Item label/content
* @slot icon - Optional leading icon for the crumb
*
* @part separator - Separator element before the item label
* @part link - The interactive anchor element
*
* @example
* ```html
* <bit-breadcrumb-item href="/">Home</bit-breadcrumb-item>
* <bit-breadcrumb-item active>Current Page</bit-breadcrumb-item>
* ```
*/
export const BREADCRUMB_ITEM_TAG = define<BitBreadcrumbItemProps>('bit-breadcrumb-item', {
props: {
active: prop.bool(),
href: prop.string(''),
separator: prop.string('/'),
},
setup(props) {
return () => html`
<li class="item" role="listitem">
<span class="separator" part="separator" aria-hidden="true">${props.separator}</span>
<a
class="link"
:href="${props.href}"
:aria-current="${() => (props.active.value ? 'page' : null)}"
:tabindex="${() => (props.active.value ? '-1' : null)}"
part="link">
<span class="icon"><slot name="icon"></slot></span>
<span class="label"><slot></slot></span>
</a>
</li>
`;
},
styles: [itemStyles],
});
// ============================================
// Breadcrumb Component
// ============================================
import componentStyles from './breadcrumb.css?inline';
/**
* Accessible breadcrumb navigation container.
*
* @element bit-breadcrumb
*
* @attr {string} label - Accessible label for the internal `<nav>` landmark
* @attr {string} separator - Separator text propagated to child `<bit-breadcrumb-item>` elements
*
* @slot - One or more `<bit-breadcrumb-item>` children
*
* @part nav - Internal navigation landmark element
* @part list - Internal ordered list container
*
* @cssprop --breadcrumb-separator - Optional custom separator text synced to child items
*
* @example
* ```html
* <bit-breadcrumb label="Page breadcrumb">
* <bit-breadcrumb-item href="/">Home</bit-breadcrumb-item>
* <bit-breadcrumb-item href="/blog">Blog</bit-breadcrumb-item>
* <bit-breadcrumb-item active>My Post</bit-breadcrumb-item>
* </bit-breadcrumb>
* ```
*/
export const BREADCRUMB_TAG = define<BitBreadcrumbProps>('bit-breadcrumb', {
props: {
label: prop.string('Breadcrumb'),
separator: prop.string(''),
},
setup(props, { host, slots }) {
// ────────────────────────────────────────────────────────────────
// Item & Separator Synchronization
// ────────────────────────────────────────────────────────────────
const getItems = (): HTMLElement[] => Array.from(host.el.getElementsByTagName('bit-breadcrumb-item'));
const syncItems = () => {
const sep = props.separator.value || '/';
const items = getItems();
for (let i = 0; i < items.length; i++) {
items[i].setAttribute('separator', sep);
items[i].toggleAttribute('data-show-separator', i > 0);
}
// Sync CSS variable for CSS-based separator (if used)
if (sep) host.el.style.setProperty('--breadcrumb-separator', `'${sep}'`);
else host.el.style.removeProperty('--breadcrumb-separator');
};
// ────────────────────────────────────────────────────────────────
// Lifecycle
// ────────────────────────────────────────────────────────────────
effect(() => {
void slots.elements().value;
syncItems();
});
return () => html`
<nav part="nav" :aria-label="${props.label}">
<ol role="list" part="list">
<slot></slot>
</ol>
</nav>
`;
},
styles: [componentStyles],
});Basic Usage
Wrap bit-breadcrumb-item elements inside bit-breadcrumb. Mark the current page with active.
<bit-breadcrumb>
<bit-breadcrumb-item href="/">Home</bit-breadcrumb-item>
<bit-breadcrumb-item href="/products">Products</bit-breadcrumb-item>
<bit-breadcrumb-item active>Sneakers</bit-breadcrumb-item>
</bit-breadcrumb>
<script type="module">
import '@vielzeug/buildit/breadcrumb';
</script>Items with Icons
Use the icon named slot on any bit-breadcrumb-item for a leading icon.
Custom Separator
Use the separator attribute to replace the default / separator. You can also override the --breadcrumb-separator CSS custom property globally in your theme.
Custom aria-label
Override the default "Breadcrumb" landmark label when a page has multiple navigation regions.
API Reference
bit-breadcrumb Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
label | string | 'Breadcrumb' | aria-label applied to the wrapping <nav> landmark |
separator | string | '/' | Separator glyph rendered between items (also sets --breadcrumb-separator) |
bit-breadcrumb Slots
| Slot | Description |
|---|---|
| (default) | bit-breadcrumb-item elements representing each crumb |
bit-breadcrumb CSS Custom Properties
| Property | Description | Default |
|---|---|---|
--breadcrumb-separator | Separator character shown between items | '/' |
bit-breadcrumb-item Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
href | string | — | URL the item links to. Omit for non-linked crumbs. |
active | boolean | false | Marks this item as the current page (aria-current="page"). Disables the link. |
bit-breadcrumb-item Slots
| Slot | Description |
|---|---|
icon | Optional leading icon or decoration |
| (default) | Crumb label text |
Events
bit-breadcrumb and bit-breadcrumb-item do not emit custom events. Use standard DOM click listeners on individual items if you need to intercept navigation (e.g., in an SPA).
Accessibility
The breadcrumb component follows WAI-ARIA best practices.
bit-breadcrumb
✅ Semantic Structure
- Renders a
<nav>element witharia-labelmatching thelabelattribute — it serves as a navigation landmark. - Items are rendered as
<li>elements inside an<ol>, conveying the sequential structure to screen readers.
✅ Screen Readers
- The
activeitem receivesaria-current="page"andaria-disabled="true"so it is announced as the current location and not activated when clicked. - The separator is rendered via CSS
content(or a hiddenaria-hiddenelement) so it is not read aloud. - When using icon-only crumbs, provide visible text as the default slot content or add
aria-labelto the icon wrapper to keep the crumb meaningful to screen readers.
Best Practices
Do:
- Always mark the final (current) crumb as
active— omitting it breaksaria-currentand confuses screen readers. - Keep crumb labels short and descriptive — they should match the
<title>or<h1>of the destination page. - Provide an
hrefon every non-active crumb so keyboard and screen reader users can navigate directly.
Don't:
- Mark more than one crumb as
active— only the current page should carryaria-current="page". - Use breadcrumbs as a replacement for primary navigation — they supplement, not replace, the main nav.
- Omit the breadcrumb on mobile viewports — breadcrumbs are even more valuable on small screens where back-navigation is harder.