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 { defineComponent, effect, html, onMount, onSlotChange } from '@vielzeug/craftit';
// ============================================
// Types
// ============================================
export type BitBreadcrumbProps = {
label?: string;
separator?: string;
};
export type BitBreadcrumbItemProps = {
active?: boolean;
href?: string;
separator?: string;
};
// ============================================
// Breadcrumb Item Component
// ============================================
import itemStyles from './breadcrumb-item.css?inline';
/**
* `bit-breadcrumb-item` — A single crumb within a `<bit-breadcrumb>` list.
*
* @example
* ```html
* <bit-breadcrumb-item href="/">Home</bit-breadcrumb-item>
* <bit-breadcrumb-item active>Current Page</bit-breadcrumb-item>
* ```
*/
export const BREADCRUMB_ITEM_TAG = defineComponent<BitBreadcrumbItemProps>({
props: {
active: { default: false },
href: { default: '' },
separator: { default: '/' },
},
setup({ props }) {
return html`
<li class="item" role="listitem">
<span class="separator" part="separator" aria-hidden="true">${() => props.separator.value || '/'}</span>
<a
class="link"
href="${() => props.href.value || undefined}"
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],
tag: 'bit-breadcrumb-item',
});
// ============================================
// Breadcrumb Component
// ============================================
import componentStyles from './breadcrumb.css?inline';
/**
* `bit-breadcrumb` — Accessible navigation breadcrumb.
*
* Wrap `<bit-breadcrumb-item>` elements as children.
* The last/current item should have `active` attribute.
*
* @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 = defineComponent<BitBreadcrumbProps>({
props: {
label: { default: 'Breadcrumb' },
separator: { default: '' },
},
setup({ host, props }) {
const getItems = (): HTMLElement[] => Array.from(host.getElementsByTagName('bit-breadcrumb-item')) as HTMLElement[];
const syncSeparatorVar = () => {
const sep = props.separator.value;
if (sep) {
host.style.setProperty('--breadcrumb-separator', `'${sep}'`);
} else {
host.style.removeProperty('--breadcrumb-separator');
}
};
const syncItems = () => {
const sep = props.separator.value || '/';
const items = getItems();
for (let i = 0; i < items.length; i += 1) {
items[i].setAttribute('separator', sep);
if (i === 0) {
items[i].removeAttribute('data-show-separator');
} else {
items[i].setAttribute('data-show-separator', '');
}
}
};
onMount(() => {
onSlotChange('default', syncItems);
// Ensure initial slotted items are normalized once on mount.
syncItems();
});
effect(syncSeparatorVar);
effect(syncItems);
return html`
<nav aria-label="${() => props.label.value}" part="nav">
<ol role="list" part="list">
<slot></slot>
</ol>
</nav>
`;
},
styles: [componentStyles],
tag: 'bit-breadcrumb',
});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.