Skip to content
Home Theme Gallery

Path

The custom element is <hui-path>.

Overview

A path bar like the Windows Explorer address bar. Each hui-path-node the server renders is a label, a link back to that level, a menu to switch to a sibling or a popover of the host’s own content, and a chevron can open a node’s children. The trail folds or becomes one menu when it does not fit, and editable lets the reader type a path.

Example

Every kind of node
Source
<hui-path label="Location">
<hui-path-node>
<a href="#files"><svg class="hui-icon" aria-hidden="true"><use href="/doc/hui/hui-icons.svg#ri-home-line"></use></svg>Home</a>
</hui-path-node>
<hui-path-node>
<a href="#projects">Projects</a>
<hui-dropdown-menu slot="children">
<a role="menuitem" href="#alpha">Alpha</a>
<a role="menuitem" href="#beta">Beta</a>
<a role="menuitem" href="#gamma">Gamma</a>
</hui-dropdown-menu>
</hui-path-node>
<hui-path-node>
<hui-dropdown-menu>
<button slot="trigger" type="button"><svg class="hui-icon" aria-hidden="true"><use href="/doc/hui/hui-icons.svg#ri-folder-line"></use></svg>Alpha</button>
<a role="menuitem" href="#alpha">Alpha</a>
<a role="menuitem" href="#beta">Beta</a>
</hui-dropdown-menu>
</hui-path-node>
<hui-path-node>
<hui-popover>
<button slot="trigger" type="button"><svg class="hui-icon" aria-hidden="true"><use href="/doc/hui/hui-icons.svg#ri-calendar-line"></use></svg>2026</button>
<div style="padding:.25rem"><hui-calendar value="2026-09-25"></hui-calendar></div>
</hui-popover>
</hui-path-node>
<hui-path-node current>Report.pdf</hui-path-node>
</hui-path>

The Go template that renders it:

<hui-path label="Location">
{{ range .Crumbs }}
<hui-path-node{{ if .Current }} current{{ end }}>
{{ if .Current }}{{ .Name }}
{{ else }}<a href="{{ .Href }}" hx-get="{{ .Href }}" hx-target="#main" hx-push-url="true">{{ .Name }}</a>{{ end }}
{{ if .HasChildren }}
<hui-dropdown-menu slot="children" hx-get="/children?of={{ .Path }}" hx-trigger="hui-open once"
hx-target="this" hx-swap="innerHTML">
<div role="menuitem" aria-disabled="true">Loading…</div>
</hui-dropdown-menu>
{{ end }}
</hui-path-node>
{{ end }}
</hui-path>

On a phone

When the trail is wider than its box, the default narrow="fold" keeps the first node and as many of the last as fit, and folds the rest behind one button whose menu lists them. narrow="menu" instead makes the whole trail one button naming the current node, whose menu is the path, indented by depth. Both are measured, reflected as data-collapsed, and undone when there is room.

Folding, or one menu 375 px wide
Source
<hui-path>
<hui-path-node><a href="#0">Home</a></hui-path-node>
<hui-path-node><a href="#1">Library</a></hui-path-node>
<hui-path-node><a href="#2">Photographs</a></hui-path-node>
<hui-path-node><a href="#3">Archive</a></hui-path-node>
<hui-path-node><a href="#4">Holidays</a></hui-path-node>
<hui-path-node current>Coast</hui-path-node>
</hui-path>
<div style="height:1rem"></div>
<hui-path narrow="menu">
<hui-path-node><a href="#0">Home</a></hui-path-node>
<hui-path-node><a href="#1">Library</a></hui-path-node>
<hui-path-node><a href="#2">Photographs</a></hui-path-node>
<hui-path-node><a href="#3">Archive</a></hui-path-node>
<hui-path-node><a href="#4">Holidays</a></hui-path-node>
<hui-path-node current>Coast</hui-path-node>
</hui-path>

API

Attributes13
Attributes
NameTypeDefaultDescription
labelstring"Path"The navigation landmark’s accessible name, and the edit field’s.
separatorstring-Text drawn between every node, such as /. Empty draws Remix’s arrow-right-s-line.
narrow"fold" | "menu""fold"What a trail wider than its box does: fold its middle behind one menu, or become one button naming the current node.
rovingbooleanfalseMakes the trail a toolbar: one tab stop and the arrow keys.
editablebooleanfalseOffers the type-a-path mode and its pencil button.
namestring-With editable, the form field the committed path submits under.
valuestring-The path as text, where the edit field starts. Server-owned; updated when the reader commits a path.
placeholderstring-The edit field’s placeholder.
edit-labelstring"Edit path"The pencil button’s accessible name.
overflow-labelstring"Show hidden path nodes"The fold button’s accessible name.
editingboolean-Reflected while the field is showing. Viewer-owned.
data-collapsedboolean-Reflected while the trail is folded or showing as one menu. Measured; never written by the host.
disabledbooleanfalseDisables editing and submits nothing.
Properties2
Properties
NameTypeDefaultDescription
valuestring-The committed path.
foldedHuiPathNode[]-Read-only. The nodes currently folded behind the overflow menu.
Methods4
Methods
NameTypeDescription
nodes()HuiPathNode[]The hui-path-node children that are part of the path, in order; a hidden node is not.
currentNode()HuiPathNode | nullThe node marked current, or the last.
startEditing()voidShows the edit field, as the pencil button does. Emits hui-path-edit.
stopEditing()voidRestores the trail without committing. Emits hui-path-edit.
Events4
Events
NameDescription
hui-path-submitEnter in the edit field. Cancellable: cancelling keeps the field open and commits nothing. Detail: { value }, the typed path.
changeA typed path was committed, after value is updated.
hui-path-editThe field opened or closed, after editing is set. Detail: { editing }.
hui-path-overflowThe trail folded, became one menu, or came back. Detail: { collapsed, folded, menu }.
Slots1
Slots
NameDescription
(default)hui-path-node elements, in order from the root. Nothing else is rendered: the nodes are assigned to the shadow root by hand, so the fold button can sit after the first.
::part() hooks9
::part() hooks
NameDescription
navThe navigation landmark.
barThe row holding the trail, the narrow menu, the field and the pencil button.
listThe trail: role="list", or role="toolbar" with roving.
overflowThe fold button’s list item and its separator.
overflow-buttonThe fold button.
menuThe narrow menu’s wrapper.
menu-buttonThe narrow menu’s button, naming the current node.
inputThe edit field.
editThe pencil button.

States

Typing a path

With editable, the pencil button - or a click on the bar’s empty space - swaps the trail for a field holding value (or the labels joined by / when there is none), selected. Enter emits a cancellable hui-path-submit, then commits: value updates, change fires, and an enclosing form is submitted with the path under name. Escape, or leaving the field, restores the trail and commits nothing.

Typing a path
Source
<form onsubmit="event.preventDefault(); this.querySelector('output').textContent = 'Submitted path=' + new FormData(this).get('path')">
<hui-path editable name="path" value="/files/projects/alpha">
<hui-path-node><a href="#files">Files</a></hui-path-node>
<hui-path-node><a href="#projects">Projects</a></hui-path-node>
<hui-path-node current>Alpha</hui-path-node>
</hui-path>
<output style="display:block;margin-top:.5rem;font-size:.875rem"></output>
</form>

One toolbar stop

With roving the trail is a role="toolbar": one tab stop, Left and Right between its links, triggers and chevrons, Home and End to the ends, Down to open a menu. Without it every control is its own tab stop, as in a breadcrumb.

One toolbar stop
Source
<hui-path roving>
<hui-path-node><a href="#home">Home</a></hui-path-node>
<hui-path-node>
<a href="#music">Music</a>
<hui-dropdown-menu slot="children">
<a role="menuitem" href="#jazz">Jazz</a>
<a role="menuitem" href="#rock">Rock</a>
</hui-dropdown-menu>
</hui-path-node>
<hui-path-node current>Jazz</hui-path-node>
</hui-path>

A text separator

separator replaces the chevron between every node with text, for a path that reads as one, like a Unix path.

A text separator
Source
<hui-path separator="/">
<hui-path-node><a href="#root">root</a></hui-path-node>
<hui-path-node><a href="#usr">usr</a></hui-path-node>
<hui-path-node current>local</hui-path-node>
</hui-path>

Server-side mechanics

The server owns the nodes, which is current, each node’s link and menu, and value. Nothing is derived that the server could disagree with: a swap of the whole trail, or of one node, is the whole update - the element reassigns the nodes, marks the last, and measures again, and any menu it had open is closed and removed from the top layer. Whether the reader is editing, and which nodes fold, are the element’s; neither is stored. Navigation is always the host’s: a node’s link, with or without hx-get, and the fold and narrow menus click that same link rather than navigating themselves. A typed path is submitted as a form field, and what it means is the server’s to decide.

Accessibility

  • The trail is a navigation landmark named by label, holding a list whose items are the nodes; the current node carries aria-current="page". With roving the list becomes a toolbar, and the nodes are presentational.
  • A separator is decorative and hidden, unless the node has children: then it is a real button with aria-haspopup="menu", aria-expanded and a name, "Contents of" the node’s label by default.
  • The fold and narrow menus are hui-dropdown-menus: arrow keys, type-ahead, Escape, and focus returned to their button.
  • Items in the fold and narrow menus activate the node’s own link, so they announce and behave as that link does.
  • In forced colours crumbs are drawn in LinkText and the buttons this element draws are bordered in ButtonText.

Keyboard

Keyboard
KeysAction
TabEach link, trigger and chevron in turn. With roving, into and out of the trail as one stop.
Left, RightWith roving, the previous or next control, not wrapping; reversed in right-to-left.
Home, EndWith roving, the first or last control.
DownOn a chevron, opens its children. With roving, on a menu trigger, opens the menu.
EnterIn the edit field, commits the path.
EscapeIn the edit field, restores the trail. In an open menu, closes it and returns to its trigger.

Gotchas

  • Only hui-path-node children are rendered. Text or other elements directly inside hui-path are ignored.
  • A menu or popover node cannot open inside the fold menu, so a folded menu node is listed as a plain entry for its href attribute, and disabled when it has none. Give such a node an href if it stands for a place.
  • Items HTMX swaps into a children menu are processed when HTMX settles, about 20ms after the menu has focused the first one. A reader pressing Enter inside that window follows the item’s href rather than its hx-get, which still reaches the same place.
  • WebKit, like Safari, leaves links out of the Tab order unless the reader turns them on, so without roving a Safari reader reaches the crumbs with Option+Tab. With roving the controls carry a tabindex and are reached either way.