Skip to content

Breadcrumb

Breadcrumb
Thymeleaf source
<sl:breadcrumb>
<sl:breadcrumb-list>
<sl:breadcrumb-item><sl:breadcrumb-link href="/">Home</sl:breadcrumb-link></sl:breadcrumb-item>
<sl:breadcrumb-separator/>
<sl:breadcrumb-item><sl:breadcrumb-link href="/components">Components</sl:breadcrumb-link></sl:breadcrumb-item>
<sl:breadcrumb-separator/>
<sl:breadcrumb-item><sl:breadcrumb-page>Breadcrumb</sl:breadcrumb-page></sl:breadcrumb-item>
</sl:breadcrumb-list>
</sl:breadcrumb>
Rendered HTML
<nav class="breadcrumb" aria-label="Breadcrumb">
<ol class="breadcrumb-list">
<li class="breadcrumb-item">
<a class="breadcrumb-link" href="/">Home</a>
</li>
<li class="breadcrumb-separator" role="presentation" aria-hidden="true">
<svg xmlns="http://www.w3.org/2000/svg" class="icon breadcrumb-separator-icon" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
<path d="m9 18 6-6-6-6"></path>
</svg>
</li>
<li class="breadcrumb-item">
<a class="breadcrumb-link" href="/components">Components</a>
</li>
<li class="breadcrumb-separator" role="presentation" aria-hidden="true">
<svg xmlns="http://www.w3.org/2000/svg" class="icon breadcrumb-separator-icon" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
<path d="m9 18 6-6-6-6"></path>
</svg>
</li>
<li class="breadcrumb-item">
<span class="breadcrumb-page" aria-current="page">Breadcrumb</span>
</li>
</ol>
</nav>

A breadcrumb is shadcn/ui’s parts: a nav holding an ordered list of levels, with a separator between them. The last level is the page shown now.

<sl:breadcrumb>
<sl:breadcrumb-list>
<sl:breadcrumb-item><sl:breadcrumb-link th:href="@{/}">Home</sl:breadcrumb-link></sl:breadcrumb-item>
<sl:breadcrumb-separator/>
<sl:breadcrumb-item><sl:breadcrumb-link th:href="@{/components}">Components</sl:breadcrumb-link></sl:breadcrumb-item>
<sl:breadcrumb-separator/>
<sl:breadcrumb-item><sl:breadcrumb-page>Breadcrumb</sl:breadcrumb-page></sl:breadcrumb-item>
</sl:breadcrumb-list>
</sl:breadcrumb>

<sl:breadcrumb-link> is a link: give it an href or th:href. <sl:breadcrumb-page> is plain text with aria-current="page". The list wraps onto a second line when there is no room, as on a phone.

In an application shell the breadcrumb goes in the page header, after the sidebar’s trigger and a vertical separator.

A level that has no page of its own, such as a group in the sidebar, is plain text in its item:

A level without a pageA level that has no page of its own is plain text in its item.
Thymeleaf source
<sl:breadcrumb aria-label="Breadcrumb with a plain level">
<sl:breadcrumb-list>
<sl:breadcrumb-item><sl:breadcrumb-link href="/">Home</sl:breadcrumb-link></sl:breadcrumb-item>
<sl:breadcrumb-separator/>
<sl:breadcrumb-item>Settings</sl:breadcrumb-item>
<sl:breadcrumb-separator/>
<sl:breadcrumb-item><sl:breadcrumb-page>Notifications</sl:breadcrumb-page></sl:breadcrumb-item>
</sl:breadcrumb-list>
</sl:breadcrumb>
Rendered HTML
<nav class="breadcrumb" aria-label="Breadcrumb with a plain level">
<ol class="breadcrumb-list">
<li class="breadcrumb-item">
<a class="breadcrumb-link" href="/">Home</a>
</li>
<li class="breadcrumb-separator" role="presentation" aria-hidden="true">
<svg xmlns="http://www.w3.org/2000/svg" class="icon breadcrumb-separator-icon" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
<path d="m9 18 6-6-6-6"></path>
</svg>
</li>
<li class="breadcrumb-item">Settings</li>
<li class="breadcrumb-separator" role="presentation" aria-hidden="true">
<svg xmlns="http://www.w3.org/2000/svg" class="icon breadcrumb-separator-icon" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
<path d="m9 18 6-6-6-6"></path>
</svg>
</li>
<li class="breadcrumb-item">
<span class="breadcrumb-page" aria-current="page">Notifications</span>
</li>
</ol>
</nav>

There is no mode that renders the levels from a list: write them with th:each, and the page shown now after them. th:block keeps item and separator together:

<sl:breadcrumb>
<sl:breadcrumb-list>
<th:block th:each="level : ${levels}">
<sl:breadcrumb-item><sl:breadcrumb-link th:href="${level.url}">[[${level.name}]]</sl:breadcrumb-link></sl:breadcrumb-item>
<sl:breadcrumb-separator/>
</th:block>
<sl:breadcrumb-item><sl:breadcrumb-page th:text="${title}">Title</sl:breadcrumb-page></sl:breadcrumb-item>
</sl:breadcrumb-list>
</sl:breadcrumb>

A long trail can leave levels out. An empty <sl:breadcrumb-ellipsis> stands for them; a screen reader says “More levels”.

Collapsed levelsAn ellipsis without content stands for the levels left out; a screen reader says "More levels".
Thymeleaf source
<sl:breadcrumb aria-label="Breadcrumb with collapsed levels">
<sl:breadcrumb-list>
<sl:breadcrumb-item><sl:breadcrumb-link href="/">Home</sl:breadcrumb-link></sl:breadcrumb-item>
<sl:breadcrumb-separator/>
<sl:breadcrumb-item><sl:breadcrumb-ellipsis/></sl:breadcrumb-item>
<sl:breadcrumb-separator/>
<sl:breadcrumb-item><sl:breadcrumb-link href="/components">Components</sl:breadcrumb-link></sl:breadcrumb-item>
<sl:breadcrumb-separator/>
<sl:breadcrumb-item><sl:breadcrumb-page>Breadcrumb</sl:breadcrumb-page></sl:breadcrumb-item>
</sl:breadcrumb-list>
</sl:breadcrumb>
Rendered HTML
<nav class="breadcrumb" aria-label="Breadcrumb with collapsed levels">
<ol class="breadcrumb-list">
<li class="breadcrumb-item">
<a class="breadcrumb-link" href="/">Home</a>
</li>
<li class="breadcrumb-separator" role="presentation" aria-hidden="true">
<svg xmlns="http://www.w3.org/2000/svg" class="icon breadcrumb-separator-icon" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
<path d="m9 18 6-6-6-6"></path>
</svg>
</li>
<li class="breadcrumb-item">
<span class="breadcrumb-ellipsis">
<svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
<circle cx="12" cy="12" r="1"></circle>
<circle cx="19" cy="12" r="1"></circle>
<circle cx="5" cy="12" r="1"></circle>
</svg>
<span class="sl-sr-only">More levels</span>
</span>
</li>
<li class="breadcrumb-separator" role="presentation" aria-hidden="true">
<svg xmlns="http://www.w3.org/2000/svg" class="icon breadcrumb-separator-icon" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
<path d="m9 18 6-6-6-6"></path>
</svg>
</li>
<li class="breadcrumb-item">
<a class="breadcrumb-link" href="/components">Components</a>
</li>
<li class="breadcrumb-separator" role="presentation" aria-hidden="true">
<svg xmlns="http://www.w3.org/2000/svg" class="icon breadcrumb-separator-icon" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
<path d="m9 18 6-6-6-6"></path>
</svg>
</li>
<li class="breadcrumb-item">
<span class="breadcrumb-page" aria-current="page">Breadcrumb</span>
</li>
</ol>
</nav>

Give the ellipsis the levels it stands for, as dropdown menu items, and it becomes a ghost icon button that opens a menu of them, as in shadcn’s example:

With a menuAn ellipsis with the hidden levels as its content is a button that opens a menu of them. Arrow keys move through the menu, Escape closes it.
Thymeleaf source
<sl:breadcrumb aria-label="Breadcrumb with a menu">
<sl:breadcrumb-list>
<sl:breadcrumb-item><sl:breadcrumb-link href="/">Home</sl:breadcrumb-link></sl:breadcrumb-item>
<sl:breadcrumb-separator/>
<sl:breadcrumb-item>
<sl:breadcrumb-ellipsis>
<sl:dropdown-menu-item as="a" href="/docs">Documentation</sl:dropdown-menu-item>
<sl:dropdown-menu-item as="a" href="/themes">Themes</sl:dropdown-menu-item>
<sl:dropdown-menu-item as="a" href="/github">GitHub</sl:dropdown-menu-item>
</sl:breadcrumb-ellipsis>
</sl:breadcrumb-item>
<sl:breadcrumb-separator/>
<sl:breadcrumb-item><sl:breadcrumb-link href="/components">Components</sl:breadcrumb-link></sl:breadcrumb-item>
<sl:breadcrumb-separator/>
<sl:breadcrumb-item><sl:breadcrumb-page>Breadcrumb</sl:breadcrumb-page></sl:breadcrumb-item>
</sl:breadcrumb-list>
</sl:breadcrumb>
Rendered HTML
<nav class="breadcrumb" aria-label="Breadcrumb with a menu">
<ol class="breadcrumb-list">
<li class="breadcrumb-item">
<a class="breadcrumb-link" href="/">Home</a>
</li>
<li class="breadcrumb-separator" role="presentation" aria-hidden="true">
<svg xmlns="http://www.w3.org/2000/svg" class="icon breadcrumb-separator-icon" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
<path d="m9 18 6-6-6-6"></path>
</svg>
</li>
<li class="breadcrumb-item">
<button class="button dropdown-menu-trigger breadcrumb-ellipsis-trigger" type="button" data-variant="ghost" data-size="icon-sm" id="breadcrumb-more-trigger" popovertarget="breadcrumb-more" aria-haspopup="menu" aria-label="Show more levels">
<svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
<circle cx="12" cy="12" r="1"></circle>
<circle cx="19" cy="12" r="1"></circle>
<circle cx="5" cy="12" r="1"></circle>
</svg>
</button>
<div class="dropdown-menu-content" id="breadcrumb-more" role="menu" popover tabindex="-1" x-data="slDropdownMenu" aria-labelledby="breadcrumb-more-trigger">
<a class="dropdown-menu-item" role="menuitem" href="/docs">Documentation</a>
<a class="dropdown-menu-item" role="menuitem" href="/themes">Themes</a>
<a class="dropdown-menu-item" role="menuitem" href="/github">GitHub</a>
</div>
</li>
<li class="breadcrumb-separator" role="presentation" aria-hidden="true">
<svg xmlns="http://www.w3.org/2000/svg" class="icon breadcrumb-separator-icon" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
<path d="m9 18 6-6-6-6"></path>
</svg>
</li>
<li class="breadcrumb-item">
<a class="breadcrumb-link" href="/components">Components</a>
</li>
<li class="breadcrumb-separator" role="presentation" aria-hidden="true">
<svg xmlns="http://www.w3.org/2000/svg" class="icon breadcrumb-separator-icon" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
<path d="m9 18 6-6-6-6"></path>
</svg>
</li>
<li class="breadcrumb-item">
<span class="breadcrumb-page" aria-current="page">Breadcrumb</span>
</li>
</ol>
</nav>
<sl:breadcrumb-item>
<sl:breadcrumb-ellipsis>
<sl:dropdown-menu-item as="a" href="/docs">Documentation</sl:dropdown-menu-item>
<sl:dropdown-menu-item as="a" href="/themes">Themes</sl:dropdown-menu-item>
</sl:breadcrumb-ellipsis>
</sl:breadcrumb-item>

The button is named “Show more levels”; an aria-label replaces it, and any other attribute goes to the button too. The menu’s id is breadcrumb-more (the button’s breadcrumb-more-trigger): give a second breadcrumb with a menu on the same page another id. The menu behaves as every dropdown menu: arrow keys, Home, End and typeahead move through the levels, Enter follows one, Escape closes it and gives the focus back to the button.

The separator is a chevron that points to the next level: to the left on a right-to-left page (dir="rtl"). Content of its own replaces it, such as another icon; that content is not turned around.

Another separatorContent of its own replaces the chevron.
Thymeleaf source
<sl:breadcrumb aria-label="Breadcrumb with slashes">
<sl:breadcrumb-list>
<sl:breadcrumb-item><sl:breadcrumb-link href="/">Home</sl:breadcrumb-link></sl:breadcrumb-item>
<sl:breadcrumb-separator><sl:icon name="slash"/></sl:breadcrumb-separator>
<sl:breadcrumb-item><sl:breadcrumb-link href="/components">Components</sl:breadcrumb-link></sl:breadcrumb-item>
<sl:breadcrumb-separator><sl:icon name="slash"/></sl:breadcrumb-separator>
<sl:breadcrumb-item><sl:breadcrumb-page>Breadcrumb</sl:breadcrumb-page></sl:breadcrumb-item>
</sl:breadcrumb-list>
</sl:breadcrumb>
Rendered HTML
<nav class="breadcrumb" aria-label="Breadcrumb with slashes">
<ol class="breadcrumb-list">
<li class="breadcrumb-item">
<a class="breadcrumb-link" href="/">Home</a>
</li>
<li class="breadcrumb-separator" role="presentation" aria-hidden="true">
<svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
<path d="M22 2 2 22"></path>
</svg>
</li>
<li class="breadcrumb-item">
<a class="breadcrumb-link" href="/components">Components</a>
</li>
<li class="breadcrumb-separator" role="presentation" aria-hidden="true">
<svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
<path d="M22 2 2 22"></path>
</svg>
</li>
<li class="breadcrumb-item">
<span class="breadcrumb-page" aria-current="page">Breadcrumb</span>
</li>
</ol>
</nav>
  • The nav is named “Breadcrumb” (sl.breadcrumb.label); an aria-label of your own replaces it, which two breadcrumbs on one page need.
  • The separators are list items hidden from assistive technology, so a screen reader counts only the levels.
  • The page shown now has aria-current="page" and is not a link. shadcn’s adds role="link" and aria-disabled="true", which a screen reader announces as a link that goes nowhere.
  • The empty ellipsis reads “More levels”, where shadcn/ui hides it from screen readers.
  • In Windows’ high-contrast mode a focused link gets an outline, as the focus ring (a shadow) is gone there.
Key English
sl.breadcrumb.label Breadcrumb
sl.breadcrumb.more More levels
sl.breadcrumb.more.trigger Show more levels

A breadcrumb built from the request’s path or from the sidebar’s menu, and structured data (BreadcrumbList) for search engines.

sl:breadcrumb declares no props; every attribute passes through.

sl:breadcrumb-list declares no props; every attribute passes through.

sl:breadcrumb-item declares no props; every attribute passes through.

sl:breadcrumb-link declares no props; every attribute passes through.

sl:breadcrumb-page declares no props; every attribute passes through.

sl:breadcrumb-separator declares no props; every attribute passes through.

AttributeValuesDefaultDescription
idstring—With content: the menu's id, breadcrumb-more when not set (the button's is &lt;id&gt;-trigger); give a second breadcrumb on the page another one. Without content: the ellipsis's own id.

Every prop can also be set from an expression: th:id="${...}". Any other attribute passes through to the rendered element, and class is appended.

Only the ellipsis with a menu needs any: it is a dropdown menu, which needs Alpine and no plugin. Without it the button still opens the menu (the browser’s popover), without the arrow keys.