Breadcrumb

Free

4 visual variants, 5 separator styles, a sibling-page dropdown switcher, badges, disabled crumbs, collapsible middle crumbs, a loading skeleton, and full keyboard support.

</>sv-breadcrumb breadcrumbnavigationpathdropdownskeleton
import { SvBreadcrumbComponent } from '@styloviz/breadcrumb';

Demo & usage

Basic usage

Usage example — open the gallery for the full interactive demo.

An interactive demo for this component lives in the gallery.

Open live demo

More examples

Collapsed with chevrons

example.htmlAngular
<sv-breadcrumb [items]="crumbs" separator="chevron" [maxVisible]="4" (crumbClick)="go($event)" />

Toggleable ellipsis

example.htmlAngular
<sv-breadcrumb [items]="crumbs" [maxVisible]="3" [expandable]="true" (expand)="onExpand()" (collapse)="onCollapse()" />

Pills variant with home icon

example.htmlAngular
<sv-breadcrumb [items]="crumbs" variant="pills" [showHomeIcon]="true" />

Sibling page switcher

example.htmlAngular
<sv-breadcrumb [items]="crumbsWithMenu" (menuItemSelect)="onSwitch($event)" />

Loading skeleton

example.htmlAngular
<sv-breadcrumb [items]="crumbs" [loading]="isLoading()" [skeletonCount]="4" />

Derived from the route, not hand-passed

example.htmlAngular
// The breadcrumb IS the route. A hand-passed array next to it is a
// second source of truth, and the gap between them is the whole
// "the breadcrumb says the wrong thing" bug class.
readonly crumbs = computed(() => svTrailFromPath(this.url(), {
  root:   { label: 'Dashboard', routerLink: '/admin' },
  base:   '/admin',
  labels: { '1042': 'Order #1042' },
}));

Router transitions instead of page loads

example.htmlAngular
// app.config.ts — @styloviz/breadcrumb has no dependency on
// @angular/router, so the router half is yours. A FACTORY: inject()
// inside href/navigate would run outside an injection context.
provideSvLinkAdapter(() => {
  const router = inject(Router);
  const seg = (t: SvLinkTarget) => (typeof t === 'string' ? [t] : [...t]);
  return {
    kind: 'angular-router',
    href: (t) => router.serializeUrl(router.createUrlTree(seg(t))),
    navigate: (t) => { void router.navigate(seg(t)); return true; },
  };
})

When to use

  • Job: destination links — renders real `<a href>` crumbs to levels of a hierarchy the route already defines (compare all seven in the “Choosing a navigation pattern” guide under Guides).
  • Showing the user's location within a nested hierarchy and offering a way back up.
  • Multi-level apps: settings, file systems, category drill-downs.
  • Use the sibling-page `menu` on a crumb when users need to jump between sibling sections (orgs, branches, workspaces) without leaving the current level.
  • For switching between sibling views, use Tabs instead.

Async states

  • loading
  • error
  • retry

Inputs

NameTypeDefaultDescription
errorSvDataError | string | nullnullLoad failure. Use `toSvDataError()` from `@styloviz/core` to normalize an `HttpErrorResponse`.
itemsBreadcrumbItem[][]Ordered list of crumbs. The last item is treated as the current page.
separatorBreadcrumbSeparator'chevron'Separator style between crumbs. Use `custom` with `customSeparatorIcon`.
customSeparatorIconstring''Icon-library NAME used when `separator="custom"` — e.g. `'chevron-right'`. Was `customSeparatorPath`, a raw `<path d="…">`, and it was written as a STATIC attribute at every call site, so the rename could not error: the custom-separator demo silently fell back to the default chevron until the sites were rewritten by hand.
variantBreadcrumbVariant'plain'Visual style of the crumb trail.
sizeBreadcrumbSize'md'Size of the breadcrumb text and icons.
maxVisiblenumber0Maximum number of crumbs to show before collapsing the middle ones. Set to `0` to always show all.
responsivebooleanfalseCollapse to fit the available width instead of to a fixed count. Overrides `maxVisible` when on.
expandablebooleantrueAllow the collapsed ellipsis to be clicked to reveal the hidden middle crumbs. When `false` the ellipsis is purely decorative.
showHomeIconbooleanfalsePrepends a home icon to the first crumb when it has no explicit `icon`.
truncateLabelsbooleanfalseClips long labels to a fixed width with an ellipsis and a title tooltip.
collapseOnMobilebooleanfalseHides middle crumbs below the `sm` breakpoint (pure CSS) to keep long trails compact on phones.
loadingbooleanfalseShows an animated skeleton in place of the crumb trail while data is loading.
skeletonCountnumber3Number of skeleton segments rendered when `loading` is `true`.
ariaLabelstring'Breadcrumb'Accessible label for the breadcrumb navigation landmark.
customClassstring''Additional CSS classes on the root `<nav>` element.

Outputs

EventPayload typeDescription
retryvoidThe host should re-run the failed load.
crumbClickBreadcrumbItemEmitted when a crumb with a `routerLink` is clicked.
expandvoidEmitted when the collapsed ellipsis is expanded to reveal hidden crumbs.
collapsevoidEmitted when an expanded trail is collapsed back via the toggle.
menuItemSelectBreadcrumbMenuItemEmitted when a sibling page is chosen from a crumb's dropdown switcher.

Wire it to your API

This component is headless about transport: it renders what you give it and reports what the user asks for. Nothing below requires @styloviz/data — drive it from whatever you already use.

Template

Bound to the names this component actually exposes.

usage.htmlAngular
<sv-breadcrumb
  [items]="data.items()"
  [loading]="data.loading()"
  [error]="data.error()"
  (retry)="data.reload()"
/>

Host class

Drive the three async inputs from your own fetch.

host.component.tsTypeScript
// This component reports its state but does not emit a query, so drive
// it from your own fetch and bind the three async inputs.
readonly items = signal<Row[]>([]);
readonly loading = signal(false);
readonly error = signal<SvDataError | null>(null);

async load(): Promise<void> {
  this.loading.set(true);
  this.error.set(null);
  try {
    this.items.set(await this.api.fetchRows());
  } catch (e) {
    this.error.set(toSvDataError(e));
  } finally {
    this.loading.set(false);
  }
}
  • This component does not emit a query, so it renders what you give it. Fetch on your own trigger and bind `items`, `loading` and `error`.
  • Normalize failures with `toSvDataError()` from `@styloviz/core`. It derives `retryable` from the status, so a 404 does not get a "Try again" button that can only fail again.

Full walkthrough: Connecting to a backend.

Customization & theming

  • Four visual variants: plain, contained, pills, underline.
  • Five separator styles, including a custom SVG path.
  • Collapse middle crumbs behind a toggleable ellipsis on overflow, or hide them below the sm breakpoint with `collapseOnMobile`.
  • Optional leading icons, an automatic home icon via `showHomeIcon`, and per-crumb badges.
  • Sibling-page dropdown switcher via a crumb's `menu` array.
  • Label truncation with a tooltip via `truncateLabels`.
  • Customisable nav landmark label via `ariaLabel`.
  • Links render through `SvLinkAdapter` from `@styloviz/core` — no dependency on `@angular/router`, so it drops into an app that routes any way at all.
  • `svTrailFromPath(path, { labels, root, base, titleCase })` derives the trail from a URL path, with no router of its own.

Best practices

  • Do: Make every crumb except the last a working link to its level. `svTrailFromPath` does this for you, and derives the trail from the current path so it cannot disagree with the route.
  • Do: Provide `provideSvLinkAdapter()` at the app root if you use a router. Without it the crumbs are still real, shareable links, but following one is a full page load — the component warns once in a dev build to say so.
  • Do: Collapse the middle on long paths: `responsive` measures the space and fits as many crumbs as it can, or `maxVisible` sets a fixed count. Collapsing keeps root, current and then the parent — the "up one level" target people reach for.
  • Do: Use `loading` with a matching `skeletonCount` while the real path resolves asynchronously, instead of rendering an empty nav.
  • Don't: Don't link the current page — mark it with aria-current (handled).
  • Tip: Set `disabled: true` on a crumb instead of omitting `routerLink` when a section exists but is permission-gated — it renders with `aria-disabled` and stays out of the tab order.
  • Tip: The `maxVisible` ellipsis is a real toggle — clicking it again after expanding collapses the middle crumbs back; listen for `expand`/`collapse` if you need to track its open state.

Accessibility

  • Wrapped in a labelled nav landmark (`ariaLabel`).
  • Current page marked with aria-current.
  • Disabled crumbs render as non-interactive elements with `aria-disabled`, outside the tab order.
  • The ellipsis toggle exposes `aria-expanded` and stays focusable in both states, so it can be opened and closed again from the keyboard without losing its place in the tab order.
  • Sibling-page dropdown follows the ARIA menu-button pattern: `aria-haspopup`, `aria-expanded`, `aria-controls`, roving tabindex, and ArrowUp/ArrowDown/Home/End/Escape keyboard support with focus returned to the trigger on close.
  • The dropdown panel is rendered in a `document.body` portal (dropdown-instruction.md pattern) so it always escapes ancestor `overflow`/transform clipping instead of being cut off inside scrollable cards or tables; it repositions on scroll and closes on viewport resize or outside click.
  • Loading state exposes `role="status"` / `aria-busy` with a screen-reader-only "Loading breadcrumb…" label.

Feedback on Breadcrumb

Spotted a gap in this page, or something that didn't work as described? Comments are GitHub Discussions in the public styloviz/community repo, so posting needs a GitHub account.

Loads giscus.app and GitHub — nothing is fetched until you click.