Skip to content

PlBreadcrumb

The trail of pages above the one being read. The last step is where the reader already is, so it stops being a link on its own, and a trail too long to read folds its middle away behind a .

React
tsx
import { PlBreadcrumb, PlBreadcrumbItem } from 'plass-ui';

<PlBreadcrumb>
  <PlBreadcrumbItem href="/">Home</PlBreadcrumbItem>
  <PlBreadcrumbItem href="/settings">Settings</PlBreadcrumbItem>
  <PlBreadcrumbItem>Billing</PlBreadcrumbItem>
</PlBreadcrumb>;
dart
import 'package:plass_ui/plass_ui.dart';

PlBreadcrumb(
  items: <PlBreadcrumbItem>[
    PlBreadcrumbItem(label: const Text('Home'), onPressed: goHome),
    PlBreadcrumbItem(label: const Text('Settings'), onPressed: goSettings),
    const PlBreadcrumbItem(label: Text('Billing')),
  ],
);

Props

PropTypeDefaultDescription
sizeshared'xs' | 'sm' | 'md' | 'lg' | 'xl''md'Type scale of the steps
colorshared'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info''primary'The colour family a link picks up when it is hovered
densityshared'default' | 'compact''default'The gap between the steps, and nothing else
separator'chevron' | 'arrow' | 'slash' | 'dot' | ReactNode'chevron'The mark between two steps. One of the four names, or any node. Four rather than a free-for-all because the difference is meaning, not decoration: chevron and arrow say "and then", slash says "path", dot says "peers of one thing"
maxItemsnumberHow many steps to show before the middle is folded away behind a …. Left out, the whole trail is shown however long it gets
itemsBeforeCollapsenumber1How many steps stay at the front of a folded trail
itemsAfterCollapsenumber1How many stay at the end
expandablebooleantrueWhether pressing the … unfolds the trail in place. Turn it off to leave the fold as a plain mark
labelstring'Breadcrumb'The name the trail is announced by. Never drawn
expandLabelstring'Show the hidden steps'What the … is announced as. Never drawn
structuredDatabooleanfalseEmits the trail a second time as a BreadcrumbList in a script tag. Off by default — a page can only have one, and many apps already emit theirs. Every step goes in, including the ones a fold is hiding
baseUrlstringWhat relative hrefs are resolved against for structuredData — the site’s origin. Search engines want an absolute URL there
childrenReactNodeThe PlBreadcrumbItems
PropTypeDefaultDescription
items * List<PlBreadcrumbItem>The steps, as a list of descriptions rather than children — the trail has to reason about which step is current and what a fold removes, and a Widget cannot be asked
sizesharedPlassSizePlassSize.mdType scale of the steps
colorsharedPlassColorPlassColor.primaryThe colour family a link picks up when it is hovered
densitysharedPlassDensityPlassDensity.standardThe gap between the steps, and nothing else
separatorPlBreadcrumbSeparatorPlBreadcrumbSeparator.chevronThe mark between two steps, as one of the four names
separatorWidgetWidget?A mark of your own, which wins over separator
maxItemsint?How many steps to show before the middle is folded away behind a …. Left out, the whole trail is shown however long it gets
itemsBeforeCollapseint1How many steps stay at the front of a folded trail
itemsAfterCollapseint1How many stay at the end
expandablebooltrueWhether pressing the … unfolds the trail in place. Turn it off to leave the fold as a plain mark
labelString'Breadcrumb'The name the trail is announced by. Never drawn
expandLabelString'Show the hidden steps'What the … is announced as. Never drawn

Every native <nav> attribute passes straight through. color is excluded from the pass-through because it is a Plass prop here.

The steps are items rather than children, and a PlBreadcrumbItem is a description rather than a widget, Flutter's own idiom, the one DataColumn and BottomNavigationBarItem use. The reason is that the trail has to reason about its steps: which one is the current page, how many there are, and which ones a fold takes out. A Widget is opaque and none of those questions can be asked of one.

There is no variant and no elevation: a trail is a line of text above the page, not a surface laid on it.

PlBreadcrumbItem

PropTypeDefaultDescription
hrefstringRenders the step as a link
onClick(event: MouseEvent) => voidFires when the step is pressed. Renders it as a button when there is no href
startIconReactNodeContent before the label — a home glyph, a repository avatar
endIconReactNodeContent after the label
currentbooleanMarks 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 for a trail that ends somewhere the reader is not — and setting it anywhere takes the mark off the last step
disabledsharedbooleanfalseUnavailable. Stops answering, keeps its place in the trail
childrenReactNodeThe step’s label
PropTypeDefaultDescription
label * WidgetWhat the step says
onPressedVoidCallback?Fires when the step is pressed. Renders it as a button when there is no href
startIconWidget?Content before the label — a home glyph, a repository avatar
endIconWidget?Content after the label
currentbool?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 for a trail that ends somewhere the reader is not — and setting it anywhere takes the mark off the last step
disabledsharedboolfalseUnavailable. Stops answering, keeps its place in the trail

Every native <li> attribute passes straight through, onto the <li> rather than onto the link inside it. size is inherited from the PlBreadcrumb around it, a step that disagreed with its neighbours about the type scale would be a trail with a hole in it.

A step carries no size of its own. The trail sets the type scale for all of them, a step that disagreed with its neighbours about it would be a trail with a hole in it.

What the shared axes (size color density) mean across the library is in prop conventions.

Examples

The current step

The last step is the page you are on, so it is not a link at all: it is announced as where the reader is and stops being pressable, even with something to press. The component works that out rather than asking every caller to remember it.

Exactly one step in a trail may carry the mark, so a step that claims it with current takes it off the last one. Doing that by hand would mean writing current={false}current: false on a step that never asked for it.

React

separator

Four named marks rather than a free-for-all, because a separator is read hundreds of times a day and the difference between them is meaning, not decoration: a chevron and an arrow say "and then", a slash says "path", a dot says "these are peers of one thing". Anything else can still be passed as a node.

The two that point are drawn once and turned, and they turn back under RTL. A trail runs the way the language does. Anything else goes in separatorseparatorWidget.

React

maxItems

A trail seven levels deep is a trail nobody reads, so the middle collapses to a that puts it back when pressed. itemsBeforeCollapse and itemsAfterCollapse decide how much stays at each end, and expandable={false}expandable: false leaves the fold as a plain mark.

The fold only happens when it actually removes something: on a three-step trail with one kept at each end, the would stand in for exactly one step, which is longer than the step it replaced.

React

structuredData

Correct markup alone is not what puts a path under a search result. The structured data is. structuredData emits the trail a second time as a schema.org BreadcrumbList, beside the <ol> rather than instead of it.

It is off by default, because a page can only have one of these and a great many apps already emit theirs from an SEO layer of their own. Turn it on where this component is the trail.

tsx
<PlBreadcrumb structuredData baseUrl="https://example.com">
  <PlBreadcrumbItem href="/">Home</PlBreadcrumbItem>
  <PlBreadcrumbItem href="/docs">Docs</PlBreadcrumbItem>
  <PlBreadcrumbItem>Breadcrumb</PlBreadcrumbItem>
</PlBreadcrumb>

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. baseUrl is what makes the URLs absolute, which is what a crawler wants.

size

React

Accessibility

  • The trail is a <nav> with an accessible name, holding an <ol>. The order is the meaning, so it is an ordered list.
  • role="list" is written out because Tailwind's reset takes the bullets off every <ol>, and Safari takes the list semantics off with them.
  • The current step carries aria-current="page" rather than "true". A trail is navigation, and the step the reader is on is a page, not the chosen one of a set of options.
  • The separators are aria-hidden: a screen reader reading "greater-than" between every step is reading the punctuation.
  • A step with only an onClick is a real <button>, and one with an href a real <a>. Neither is a <span> with a handler on it.
  • The trail is a named group, and label is that name.
  • A step that goes somewhere is announced as a link, which is what puts it in a screen reader's list of links. Enter follows it; Space deliberately does not.
  • The current step is announced as a heading rather than as a link. It is where the reader is, not somewhere to go.
  • The separators are excluded from semantics: a screen reader reading "greater-than" between every step is reading the punctuation.
  • The is a real focus stop with a name of its own, so a folded trail can be opened from a keyboard.

Differences from the React build

ReactFlutterWhy
<PlBreadcrumbItem> childrenitems, as descriptionsThe trail has to reason about its steps, which is current, how many there are, which the fold takes out. A Widget is opaque; a description is not.
hrefonPressedFlutter has no link element. A step that navigates calls your router.
aria-current="page"announced as a headingFlutter's semantics tree has no current. A heading is the nearest true thing: this is the place, not a way to it.
structuredData, baseUrlThere is no crawler reading a Flutter app, and no <script type="application/ld+json"> to put a BreadcrumbList in.
separator as name-or-nodeseparator and separatorWidgetDart has no union type, so the named marks and a mark of your own are two parameters.
children on a steplabelIt is the one slot, and naming it is what lets a step be a description.

Released under the MIT License