Breadcrumb
The trail of pages above the one being read. Use it wherever a screen sits inside a hierarchy the reader may want to climb back out of.
import { Breadcrumb, BreadcrumbItem } from 'neba';
<Breadcrumb>
<BreadcrumbItem href="/">Home</BreadcrumbItem>
<BreadcrumbItem href="/projects">Projects</BreadcrumbItem>
<BreadcrumbItem>Settings</BreadcrumbItem>
</Breadcrumb>;Props
Breadcrumb
| Prop | Type | Default | Description |
|---|---|---|---|
| locale | string | — | BCP 47 tag naming the nav landmark and the … button |
| sizeshared | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'md' | Height and type scale |
| colorshared | 'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info' | 'primary' | Semantic colour role. Arbitrary colour values are not accepted |
| densityshared | 'default' | 'compact' | 'default' | The space between steps only |
| separator | 'chevron' | 'arrow' | 'slash' | 'dot' | ReactNode | 'chevron' | What is drawn between two steps: one of the four names, or any node. chevron and arrow say "and then", slash says "path", dot says "peers of one thing" |
| maxItems | number | — | How many steps to show before the middle is folded away behind a `…`. Left out, the whole trail is shown however long it gets |
| itemsBeforeCollapse | number | 1 | How many steps stay at the front of a folded trail |
| itemsAfterCollapse | number | 1 | How many stay at the end |
| expandable | boolean | true | Whether pressing the `…` unfolds the trail in place. Turn it off to leave the fold as a plain mark |
| label | string | — | The name the trail is announced by, as the nav's aria-label |
| expandLabel | string | — | What the `…` is announced as |
| structuredData | boolean | false | Also emits a schema.org BreadcrumbList as JSON-LD, folded steps included |
| baseUrl | string | — | What relative hrefs are resolved against for structuredData: the site's origin |
| children | ReactNode | — | The BreadcrumbItems |
Every other <nav> attribute passes through. The shared axes are in prop conventions.
BreadcrumbItem
| Prop | Type | Default | Description |
|---|---|---|---|
| href | string | — | Renders the step as a link |
| onClick | MouseEventHandler<HTMLElement> | — | Fires when the step is pressed. Renders it as a button when there is no href |
| startIcon | ReactNode | — | Content before the label: a home glyph, a repository avatar |
| endIcon | ReactNode | — | Content after the label |
| current | boolean | — | Marks this step as the page you are on, which stops it being a link. The last step is the current one on its own, so this is only needed for a trail that ends somewhere the reader is not, and setting it anywhere takes the mark off the last step, because only one step in a trail can be it |
| disabled | boolean | false | Unavailable. Keeps its place in the trail |
| children | ReactNode | — | The step's label |
Every other <li> attribute passes through to the step.
Examples
separator
separator takes one of four names (chevron, arrow, slash, dot), or any node. The two that point turn back under RTL.
import { Breadcrumb, BreadcrumbItem } from 'neba';
export default function BreadcrumbSeparators() {
return (
<div className="flex flex-col gap-3">
{(['chevron', 'arrow', 'slash', 'dot'] as const).map((separator) => (
<Breadcrumb key={separator} separator={separator} label={separator}>
<BreadcrumbItem href="#home">Home</BreadcrumbItem>
<BreadcrumbItem href="#projects">Projects</BreadcrumbItem>
<BreadcrumbItem>{separator}</BreadcrumbItem>
</Breadcrumb>
))}
{/* Anything else is drawn as it is given. */}
<Breadcrumb separator="»" label="custom">
<BreadcrumbItem href="#home">Home</BreadcrumbItem>
<BreadcrumbItem href="#projects">Projects</BreadcrumbItem>
<BreadcrumbItem>custom</BreadcrumbItem>
</Breadcrumb>
</div>
);
}maxItems
A trail past maxItems steps folds its middle away behind a …, which puts it back when pressed. itemsBeforeCollapse and itemsAfterCollapse decide how many stay at each end; both default to 1. expandable={false} leaves the fold as a plain mark.
The fold only happens when it removes more than one step, since standing in for a single step makes the trail longer rather than shorter.
import { Breadcrumb, BreadcrumbItem } from 'neba';
export default function BreadcrumbCollapse() {
return (
<div className="flex flex-col gap-4">
{/* One step at each end, everything between folded behind the `…`. */}
<Breadcrumb maxItems={3} label="Folded">
<BreadcrumbItem href="#home">Home</BreadcrumbItem>
<BreadcrumbItem href="#org">Acme</BreadcrumbItem>
<BreadcrumbItem href="#team">Platform</BreadcrumbItem>
<BreadcrumbItem href="#repo">neba</BreadcrumbItem>
<BreadcrumbItem href="#branch">main</BreadcrumbItem>
<BreadcrumbItem>Button.tsx</BreadcrumbItem>
</Breadcrumb>
{/* Two kept at the front, two at the end. */}
<Breadcrumb maxItems={4} itemsBeforeCollapse={2} itemsAfterCollapse={2} label="Wider ends">
<BreadcrumbItem href="#home">Home</BreadcrumbItem>
<BreadcrumbItem href="#org">Acme</BreadcrumbItem>
<BreadcrumbItem href="#team">Platform</BreadcrumbItem>
<BreadcrumbItem href="#repo">neba</BreadcrumbItem>
<BreadcrumbItem href="#branch">main</BreadcrumbItem>
<BreadcrumbItem>Button.tsx</BreadcrumbItem>
</Breadcrumb>
</div>
);
}The current step
The last step is the page you are on, so it is not a link even when it is given an href. current on an earlier step moves that mark, and takes it off the last one.
startIcon
import { Breadcrumb, BreadcrumbItem } from 'neba';
function HomeIcon() {
return (
<svg viewBox="0 0 16 16" fill="none">
<path
d="M2.5 7 8 2.5 13.5 7v6a.5.5 0 0 1-.5.5h-3v-4H6v4H3a.5.5 0 0 1-.5-.5V7Z"
stroke="currentColor"
strokeWidth="1.5"
strokeLinejoin="round"
/>
</svg>
);
}
function FileIcon() {
return (
<svg viewBox="0 0 16 16" fill="none">
<path d="M4 2h5l3 3v9H4V2Z" stroke="currentColor" strokeWidth="1.5" strokeLinejoin="round" />
<path d="M9 2v3h3" stroke="currentColor" strokeWidth="1.5" strokeLinejoin="round" />
</svg>
);
}
export default function BreadcrumbIcons() {
return (
<Breadcrumb size="lg">
<BreadcrumbItem href="#home" startIcon={<HomeIcon />}>
Home
</BreadcrumbItem>
<BreadcrumbItem href="#src">src</BreadcrumbItem>
<BreadcrumbItem startIcon={<FileIcon />}>Button.tsx</BreadcrumbItem>
</Breadcrumb>
);
}size
import { Breadcrumb, BreadcrumbItem } from 'neba';
export default function BreadcrumbSizes() {
return (
<div className="flex flex-col gap-3">
{(['xs', 'sm', 'md', 'lg', 'xl'] as const).map((size) => (
<Breadcrumb key={size} size={size} label={size}>
<BreadcrumbItem href="#home">Home</BreadcrumbItem>
<BreadcrumbItem href="#projects">Projects</BreadcrumbItem>
<BreadcrumbItem>{size}</BreadcrumbItem>
</Breadcrumb>
))}
</div>
);
}structuredData
Correct markup alone is not what puts a path under a search result: the structured data is. Turning structuredData on emits a schema.org BreadcrumbList in a <script type="application/ld+json"> beside the trail. baseUrl is what relative hrefs are resolved against, since a search engine wants an absolute URL.
Every step goes in, including the ones a maxItems fold is hiding: what is collapsed is a matter of how much room the row has, and the path is the path either way. A step with no href is emitted without an item, which is the last step's usual case.
It is off by default. A page can only have one BreadcrumbList, and a great many apps already emit theirs from an SEO layer of their own.
import { Breadcrumb, BreadcrumbItem } from 'neba';
/**
* The preview looks exactly like any other trail, which is the point: what
* `structuredData` adds is a `<script type="application/ld+json">` beside the
* markup, drawn for nobody and read by a crawler. Open the inspector to see it.
*/
export default function BreadcrumbStructuredData() {
return (
<Breadcrumb structuredData baseUrl="https://neba.cdget.com">
<BreadcrumbItem href="/">Home</BreadcrumbItem>
<BreadcrumbItem href="/components/">Components</BreadcrumbItem>
<BreadcrumbItem href="/components/display/">Display</BreadcrumbItem>
<BreadcrumbItem>Breadcrumb</BreadcrumbItem>
</Breadcrumb>
);
}Accessibility
- The trail is a
navnamed bylabel, which defaults toBreadcrumb, holding an ordered list. - The current step carries
aria-current="page", and exactly one step in a trail ever does. - The separators are
aria-hidden, so a reader hears the steps and not the punctuation between them. - The
…is a real button named byexpandLabel. Withexpandable={false}it is a mark and is hidden from readers. localenames the nav landmark and the…button;labelandexpandLabelwrite them out instead.structuredDatais not an accessibility feature: it is read by a crawler and draws nothing on screen.