Breadcrumb
Trail navigation that collapses the middle into an ellipsis when the path gets long. The full trail lives in state.trail; visible() derives the rendered segments. Clicking the ellipsis expands the trail in place.
Live preview
Markup
<nav class="breadcrumb" data-component="breadcrumb-demo" aria-label="Breadcrumb">
<template data-each="visible()" data-key="key">
<span data-if="item.ellipsis">
<button class="crumb-ellipsis" @click="expand">…</button>
<span class="crumb-sep" aria-hidden="true">›</span>
</span>
<span data-if="!item.ellipsis && !item.current">
<a data-bind="href:item.href" data-text="item.label"></a>
<span class="crumb-sep" aria-hidden="true">›</span>
</span>
<span class="crumb-current"
data-if="item.current"
aria-current="page"
data-text="item.label"></span>
</template>
</nav>
Definition
Micra.define('breadcrumb-demo', {
state: {
trail: [
{ label: 'Workspace', href: '/ws' },
{ label: 'Projects', href: '/ws/projects' },
{ label: 'Atlas', href: '/ws/projects/atlas' },
{ label: 'Settings', href: '/ws/projects/atlas/settings' },
{ label: 'Billing', href: '/ws/projects/atlas/settings/billing' },
],
showAll: false,
maxSegments: 4, // collapse if trail exceeds this
},
visible() {
const { trail, showAll, maxSegments } = this.state
const len = trail.length
const tail = (item, i) => ({ ...item, current: i === len - 1, key: 'c-' + i })
if (showAll || len <= maxSegments) {
return trail.map(tail)
}
// Show: first + ellipsis + last 2
return [
tail(trail[0], 0),
{ ellipsis: true, key: 'ellipsis' },
tail(trail[len - 2], len - 2),
tail(trail[len - 1], len - 1),
]
},
expand() { this.state.showAll = true },
setShort() {
this.state.showAll = false
this.state.trail = [
{ label: 'Workspace', href: '/ws' },
{ label: 'Projects', href: '/ws/projects' },
{ label: 'Atlas', href: '/ws/projects/atlas' },
]
},
setLong() {
this.state.showAll = false
this.state.trail = [
{ label: 'Workspace', href: '/ws' },
{ label: 'Projects', href: '/ws/projects' },
{ label: 'Atlas', href: '/ws/projects/atlas' },
{ label: 'Settings', href: '/ws/projects/atlas/settings' },
{ label: 'Billing', href: '/ws/projects/atlas/settings/billing' },
]
},
})
Integration
- Trail is just data. Render server-side, load from the router, or hydrate from a fetch — replace state.trail and the breadcrumb updates.
- Collapse threshold (maxSegments) is state, so you can change it per viewport — pair with a window.matchMedia listener in onCreate for responsive collapse.
- ARIA. aria-label="Breadcrumb" on the <nav> and aria-current="page" on the last segment are the two attributes assistive tech actually consumes.
- Keys. Each segment carries a key (the index or 'ellipsis') so keyed data-each survives expand/collapse without unnecessary DOM churn.
Pitfalls
- Don't store collapsed as a state field redundant with showAll — derive whatever you need from the existing flag.
- Don't put the chevron inside the <a> — it then becomes a click target for the link, which surprises users who land on a pixel between segments.
- Don't compute visible() by mutating the trail. The { ...item, current } spread creates fresh objects so the source array stays untouched.