Skip to content
Home Theme Gallery

Navigation menu

The custom element is <hui-navigation-menu>.

Overview

A list of real links, optionally with dropdown panels, where the current item is the server’s aria-current and the open panel is the reader’s.

Example

Links with a panel
Source
<hui-navigation-menu label="Main">
<a class="hui-nav-link" href="#home" aria-current="page">Home</a>
<a class="hui-nav-link" href="#products" data-value="products">Products</a>
<a class="hui-nav-link" href="#pricing">Pricing</a>
<div data-panel data-for="products" style="padding:.75rem">Product links live here.</div>
</hui-navigation-menu>

The Go template that renders it:

<hui-navigation-menu label="Main">
{{ range .Items }}
<a class="hui-nav-link" href="{{ .Href }}"{{ if .Current }} aria-current="page"{{ end }}{{ if .Value }} data-value="{{ .Value }}"{{ end }}>{{ .Label }}</a>
{{ end }}
<div data-panel data-for="products">…</div>
</hui-navigation-menu>

On a phone

When its links do not fit on one row, the bar folds behind a single button (its text is menu-label) and the links stack in a dropdown under it; it unfolds once there is room for the row. An item with a panel opens it inline, under the item. At any width, a tap or Enter on an item with a panel opens the panel rather than following the link - with JavaScript off, the link still works.

Five links fold behind one button 375 px wide
Source
<hui-navigation-menu label="Main">
<a class="hui-nav-link" href="#home" aria-current="page">Home</a>
<a class="hui-nav-link" href="#components" data-value="components">Components</a>
<a class="hui-nav-link" href="#docs">Documentation</a>
<a class="hui-nav-link" href="#blocks">Blocks</a>
<a class="hui-nav-link" href="#pricing">Pricing</a>
<div data-panel data-for="components" style="padding:.5rem">
<a class="hui-nav-link" href="#alert-dialog">Alert dialog</a>
<a class="hui-nav-link" href="#hover-card">Hover card</a>
<a class="hui-nav-link" href="#progress">Progress</a>
</div>
</hui-navigation-menu>

API

Attributes6
Attributes
NameTypeDefaultDescription
orientation"horizontal" | "vertical""horizontal"Layout and which arrows move.
opena <code>data-value</code>""Which panel is open. Reflected, so a server may render one open.
delaynumber150Hover intent in, in milliseconds.
labelstring"Main"The accessible name of the <nav>.
menu-labelstring"Menu"The text of the button the links fold behind when they do not fit on one row.
data-collapsedbooleanabsentSet by the element, never the host, while the links are folded behind the button.
Properties3
Properties
NameTypeDefaultDescription
orientationstring'horizontal'Reflects to orientation.
openstring''Reflects to open.
delaynumber150Hover intent delay.
Events2
Events
NameDescription
hui-openA panel opened. Detail: { value }.
hui-closeA panel closed. Detail: { value }.
Slots1
Slots
NameDescription
(default)The links and their [data-panel][data-for] panels.
Classes1
Classes
NameDescription
.hui-nav-linkA link inside hui-navigation-menu. The current one carries the host’s aria-current="page".
<a class="hui-nav-link" href="/entries" aria-current="page">Entries</a>
::part() hooks3
::part() hooks
NameDescription
listThe <nav> wrapping the links; the dropdown when they are folded.
toggleThe button the links fold behind when they do not fit. Hidden otherwise.
viewportThe positioned region the panel appears in.

States

This element has no states beyond its default.

Server-side mechanics

The server owns the items, their hrefs, which is current (aria-current="page"), and the panel contents; it may render a panel open with open. The element is not form-associated and submits nothing. The open panel is viewer-owned and announced with hui-open/hui-close. A swap that removes the element while a panel is open closes it with the element, leaving nothing behind.

Accessibility

  • The list is a <nav> landmark with a name from label.
  • Every item is a real link, so it can be opened in a new tab and works with JavaScript disabled.
  • A panel-owning link carries aria-expanded; the current one carries the server’s aria-current="page".

Keyboard

Keyboard
KeysAction
Arrow Left/Right (or Up/Down)Moves focus between the top-level links.
EnterFollows the link; on an item with a panel, opens the panel and moves focus to its first link.
EscapeCloses the open panel and returns focus to its link; then closes the folded menu and returns focus to its button.

Gotchas

  • Panels are light-DOM [data-panel][data-for] children, not a slot; the element shows, hides and positions them. They must sit inside the element.
  • Navigation is links, not buttons that navigate: the dropdown is an enhancement over links that already work.
  • A click or tap on an item that owns a panel opens the panel and does not navigate, as a Radix trigger is a button. Give the panel a link to the item’s own page if it has one. With JavaScript off the item is a plain link again.