Skip to content

cv-breadcrumb ​

Navigation landmark that displays a trail of links showing the user's current location within a hierarchical structure.

Headless: createBreadcrumb

Usage ​

View source
html
<div class="breadcrumb-demo-shell" data-demo="breadcrumb">
  <section class="breadcrumb-demo-hero" aria-labelledby="breadcrumb-demo-title">
    <div class="breadcrumb-demo-copy">
      <span class="breadcrumb-demo-kicker">Navigation landmark</span>
      <h3 id="breadcrumb-demo-title">Show where the user is, not what they should do next.</h3>
      <p>
        Breadcrumbs expose one hierarchy trail, keep the current page marked with `aria-current`, and let each
        segment remain a normal link.
      </p>
    </div>

    <dl class="breadcrumb-demo-metrics" aria-label="Breadcrumb behavior summary">
      <div>
        <dt>Root</dt>
        <dd>navigation landmark</dd>
      </div>
      <div>
        <dt>Current</dt>
        <dd>aria-current page</dd>
      </div>
      <div>
        <dt>Slots</dt>
        <dd>prefix, suffix, separator</dd>
      </div>
    </dl>
  </section>

  <section class="breadcrumb-demo-section" aria-labelledby="breadcrumb-demo-default-title">
    <div class="breadcrumb-demo-section-header">
      <span class="breadcrumb-demo-kicker">Default path</span>
      <h4 id="breadcrumb-demo-default-title">The last item becomes current when no value is set</h4>
    </div>

    <div class="breadcrumb-demo-panel breadcrumb-demo-panel--primary">
      <cv-breadcrumb aria-label="Vault path">
        <cv-breadcrumb-item value="workspace" href="#workspace">
          <cv-icon name="database" size="s" slot="prefix"></cv-icon>
          Workspace
        </cv-breadcrumb-item>
        <cv-breadcrumb-item value="vaults" href="#vaults">Vaults</cv-breadcrumb-item>
        <cv-breadcrumb-item value="client-keys" href="#client-keys">Client keys</cv-breadcrumb-item>
      </cv-breadcrumb>
    </div>
  </section>

  <section
    class="breadcrumb-demo-section breadcrumb-demo-controlled"
    aria-labelledby="breadcrumb-demo-controlled-title"
  >
    <div class="breadcrumb-demo-section-header">
      <span class="breadcrumb-demo-kicker">Controlled current item</span>
      <h4 id="breadcrumb-demo-controlled-title">Set `value` when the route state owns the active segment</h4>
    </div>

    <div class="breadcrumb-demo-panel">
      <cv-breadcrumb value="vaults" aria-label="Controlled path">
        <cv-breadcrumb-item value="workspace" href="#workspace">Workspace</cv-breadcrumb-item>
        <cv-breadcrumb-item value="vaults" href="#vaults">Vaults</cv-breadcrumb-item>
        <cv-breadcrumb-item value="client-keys" href="#client-keys">Client keys</cv-breadcrumb-item>
      </cv-breadcrumb>
      <output class="breadcrumb-demo-output" aria-live="polite">Current value: vaults</output>
    </div>
  </section>

  <section class="breadcrumb-demo-section" aria-labelledby="breadcrumb-demo-slots-title">
    <div class="breadcrumb-demo-section-header">
      <span class="breadcrumb-demo-kicker">Overflow and slots</span>
      <h4 id="breadcrumb-demo-slots-title">Keep long paths on one rail; show slot decoration separately</h4>
    </div>

    <div class="breadcrumb-demo-grid">
      <div class="breadcrumb-demo-panel breadcrumb-demo-panel--path breadcrumb-demo-panel--wide">
        <div class="breadcrumb-demo-panel-heading">
          <span class="breadcrumb-demo-label">Overflow rail</span>
          <p class="breadcrumb-demo-note">
            Long middle segments truncate on one line instead of turning into paragraph-like rows.
          </p>
        </div>
        <cv-breadcrumb aria-label="Long vault path">
          <cv-breadcrumb-item value="files" href="#files">Files</cv-breadcrumb-item>
          <cv-breadcrumb-item value="case-folder" href="#case-folder">
            Cross-border investigation workspace
          </cv-breadcrumb-item>
          <cv-breadcrumb-item value="evidence-package" href="#evidence-package">
            Evidence package with very long archive name
          </cv-breadcrumb-item>
        </cv-breadcrumb>
      </div>

      <div class="breadcrumb-demo-panel breadcrumb-demo-panel--path">
        <div class="breadcrumb-demo-panel-heading">
          <span class="breadcrumb-demo-label">Prefix and suffix</span>
          <p class="breadcrumb-demo-note">Affixes support a segment without changing the path rhythm.</p>
        </div>
        <cv-breadcrumb aria-label="Affix path">
          <cv-breadcrumb-item value="vaults" href="#vaults">
            <cv-icon name="database" size="s" slot="prefix"></cv-icon>
            Vaults
          </cv-breadcrumb-item>
          <cv-breadcrumb-item value="access" href="#access">
            Access policy
            <span slot="suffix">beta</span>
          </cv-breadcrumb-item>
        </cv-breadcrumb>
      </div>

      <div class="breadcrumb-demo-panel breadcrumb-demo-panel--path">
        <div class="breadcrumb-demo-panel-heading">
          <span class="breadcrumb-demo-label">Custom separator</span>
          <p class="breadcrumb-demo-note">A custom separator should still read as one continuous path.</p>
        </div>
        <cv-breadcrumb aria-label="Settings path">
          <cv-breadcrumb-item value="settings" href="#settings">
            Settings
            <span slot="separator">→</span>
          </cv-breadcrumb-item>
          <cv-breadcrumb-item value="access" href="#access">Access policy</cv-breadcrumb-item>
        </cv-breadcrumb>
      </div>
    </div>
  </section>
</div>

<script>
  document
    .querySelectorAll('.breadcrumb-demo-shell[data-demo="breadcrumb"]:not([data-ready])')
    .forEach((shell) => {
      shell.dataset.ready = 'true'
      const breadcrumb = shell.querySelector('.breadcrumb-demo-controlled cv-breadcrumb')
      const output = shell.querySelector('.breadcrumb-demo-output')

      breadcrumb?.addEventListener('click', (event) => {
        const item = event
          .composedPath()
          .find(
            (target) =>
              target instanceof HTMLElement && target.tagName.toLowerCase() === 'cv-breadcrumb-item',
          )

        if (!item) return
        event.preventDefault()
        if (item.current) return
        breadcrumb.value = item.value
        output.textContent = `Current value: ${item.value}`
      })
    })
</script>

Anatomy ​

<cv-breadcrumb> (host)
└── <nav part="base" role="navigation" aria-label="Breadcrumb">
    └── <ol part="list">
        └── <slot>                              ← cv-breadcrumb-item elements

Attributes ​

AttributeTypeDefaultDescription
valueString""Value of the current (active) breadcrumb item. Reflects and controls which item has aria-current="page".
aria-labelString"Breadcrumb"Accessible label for the navigation landmark
aria-labelledbyString""ID of an element that labels the navigation landmark. When set, aria-label is omitted.

Slots ​

SlotDescription
(default)cv-breadcrumb-item elements

CSS Parts ​

PartElementDescription
base<nav>Navigation landmark wrapper
list<ol>Ordered list container for breadcrumb items

CSS Custom Properties ​

PropertyDefaultDescription
--cv-breadcrumb-gapvar(--cv-space-2, 8px)Gap between breadcrumb items
--cv-breadcrumb-row-gapvar(--cv-space-1, 4px)Row gap when breadcrumb items wrap

Events ​

No component-specific events. Navigation uses standard link click behavior.

Child Elements ​

cv-breadcrumb-item ​

Anatomy ​

<cv-breadcrumb-item> (host)
├── <span part="prefix">
│   └── <slot name="prefix">
├── <a part="link" role="link" href="…" aria-current="page"?>
│   └── <slot>                                  ← label text
├── <span part="suffix">
│   └── <slot name="suffix">
└── <span part="separator" aria-hidden="true">
    └── <slot name="separator">/</slot>

Attributes ​

AttributeTypeDefaultDescription
valueString""Unique identifier for this item. Auto-generated as item-{n} if empty.
hrefString""URL destination. Defaults to # if empty.
currentBooleanfalseWhether this item represents the current page. Managed by parent, reflects aria-current="page" on the link.
show-separatorBooleantrueWhether the separator is visible. Managed by parent — hidden on the last item.

Internal property (not reflected):

PropertyTypeDefaultDescription
linkIdString""DOM id for the link element, set by parent from headless contract

Slots ​

SlotDescription
(default)Label text
prefixIcon or element before the label
suffixIcon or element after the label
separatorSeparator between items. Default: /

CSS Parts ​

PartElementDescription
link<a>The anchor element
prefix<span>Prefix container
suffix<span>Suffix container
separator<span>Separator container (has aria-hidden="true")

CSS Custom Properties ​

PropertyDefaultDescription
--cv-breadcrumb-item-gapvar(--cv-space-2, 8px)Gap between prefix, link, suffix, and separator
--cv-breadcrumb-item-font-sizevar(--cv-font-size-sm, .875rem)Item label font size
--cv-breadcrumb-item-link-max-inline-size18remMaximum width before label truncation
--cv-breadcrumb-item-link-min-block-size28pxMinimum link target height
--cv-breadcrumb-item-link-padding-block4pxLink block-axis padding
--cv-breadcrumb-item-link-padding-inline7pxLink inline-axis padding
--cv-breadcrumb-item-link-radiusvar(--cv-radius-1, 6px)Link border radius
--cv-breadcrumb-item-colorvar(--cv-color-text-muted)Default item text color
--cv-breadcrumb-item-hover-backgroundvar(--cv-color-surface-hover)Hover background for non-current items
--cv-breadcrumb-item-hover-colorvar(--cv-color-text)Hover text color for non-current items
--cv-breadcrumb-item-focus-ringvar(--cv-color-focus-ring)Focus-visible outline color
--cv-breadcrumb-item-separator-opacity0.62Opacity of the separator
--cv-breadcrumb-item-current-backgroundvar(--cv-color-primary-surface)Current item background
--cv-breadcrumb-item-current-border-colorvar(--cv-color-primary-border)Current item border color
--cv-breadcrumb-item-current-colorvar(--cv-color-text-strong)Current item text color
--cv-breadcrumb-item-current-font-weightvar(--cv-font-weight-semibold, 600)Font weight of the current item's link

Visual States ​

Host selectorDescription
:host([current])Current page — link has highlighted surface styles and aria-current="page"
:host(:not([show-separator]))Separator hidden (last item)
[part="link"]:focus-visibleKeyboard focus ring on the interactive link

ChromVoid UIKit documentation