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
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.
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
| Name | Type | Default | Description |
|---|---|---|---|
label | string | "Path" | The navigation landmark’s accessible name, and the edit field’s. |
separator | string | - | 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. |
roving | boolean | false | Makes the trail a toolbar: one tab stop and the arrow keys. |
editable | boolean | false | Offers the type-a-path mode and its pencil button. |
name | string | - | With editable, the form field the committed path submits under. |
value | string | - | The path as text, where the edit field starts. Server-owned; updated when the reader commits a path. |
placeholder | string | - | The edit field’s placeholder. |
edit-label | string | "Edit path" | The pencil button’s accessible name. |
overflow-label | string | "Show hidden path nodes" | The fold button’s accessible name. |
editing | boolean | - | Reflected while the field is showing. Viewer-owned. |
data-collapsed | boolean | - | Reflected while the trail is folded or showing as one menu. Measured; never written by the host. |
disabled | boolean | false | Disables editing and submits nothing. |
Properties2
| Name | Type | Default | Description |
|---|---|---|---|
value | string | - | The committed path. |
folded | HuiPathNode[] | - | Read-only. The nodes currently folded behind the overflow menu. |
Methods4
| Name | Type | Description |
|---|---|---|
nodes() | HuiPathNode[] | The hui-path-node children that are part of the path, in order; a hidden node is not. |
currentNode() | HuiPathNode | null | The node marked current, or the last. |
startEditing() | void | Shows the edit field, as the pencil button does. Emits hui-path-edit. |
stopEditing() | void | Restores the trail without committing. Emits hui-path-edit. |
Events4
| Name | Description |
|---|---|
hui-path-submit | Enter in the edit field. Cancellable: cancelling keeps the field open and commits nothing. Detail: { value }, the typed path. |
change | A typed path was committed, after value is updated. |
hui-path-edit | The field opened or closed, after editing is set. Detail: { editing }. |
hui-path-overflow | The trail folded, became one menu, or came back. Detail: { collapsed, folded, menu }. |
Slots1
| Name | Description |
|---|---|
(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
| Name | Description |
|---|---|
nav | The navigation landmark. |
bar | The row holding the trail, the narrow menu, the field and the pencil button. |
list | The trail: role="list", or role="toolbar" with roving. |
overflow | The fold button’s list item and its separator. |
overflow-button | The fold button. |
menu | The narrow menu’s wrapper. |
menu-button | The narrow menu’s button, naming the current node. |
input | The edit field. |
edit | The 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.
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.
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.
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
navigationlandmark named bylabel, holding a list whose items are the nodes; the current node carriesaria-current="page". Withrovingthe list becomes atoolbar, 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-expandedand 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
LinkTextand the buttons this element draws are bordered inButtonText.
Keyboard
| Keys | Action |
|---|---|
Tab | Each link, trigger and chevron in turn. With roving, into and out of the trail as one stop. |
Left, Right | With roving, the previous or next control, not wrapping; reversed in right-to-left. |
Home, End | With roving, the first or last control. |
Down | On a chevron, opens its children. With roving, on a menu trigger, opens the menu. |
Enter | In the edit field, commits the path. |
Escape | In the edit field, restores the trail. In an open menu, closes it and returns to its trigger. |
Gotchas
- Only
hui-path-nodechildren are rendered. Text or other elements directly insidehui-pathare 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
hrefattribute, and disabled when it has none. Give such a node anhrefif 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
hrefrather than itshx-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
rovinga Safari reader reaches the crumbs with Option+Tab. Withrovingthe controls carry a tabindex and are reached either way.