Nav

A bar with a brand, a list of links, and actions, that collapses the links behind a toggle at its own width and opens them as a sheet, a drawer, or a screen.

Example

View Code
<nav class="nav" aria-label="Site">
    <a href="#" data-brand>Yeti</a>
    <button type="button" popovertarget="site-menu" aria-label="Menu"><svg aria-hidden="true" viewBox="0 0 16 16"><path d="M2 4h12M2 8h12M2 12h12" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg></button>
    <ul id="site-menu" popover role="list">
        <li><a href="#" aria-current="page">Docs</a></li>
        <li><a href="#">Blog</a></li>
        <li><a href="#">About</a></li>
    </ul>
    <div data-actions><a class="button" href="#" data-size="sm">Get started</a></div>
</nav>

When to use it

The bar across the top of a site: the name, the main links, a button or two. It is a bar of links, not a menu system. Sections with their own submenus are the dropdown's job, and a column of links down the side of a page is a stack inside a sidebar, which needs no component.

How it works

One list of links, two modes. The list carries popover and the button carries popovertarget naming it. When the bar is narrower than data-threshold (its own width, not the screen's, so a nav in a narrow column collapses while the same one across a page does not), the list is a closed popover and the button opens it; light dismiss, Escape, and the button's expanded state all come from the browser. At the threshold and above, the nav's own container query puts the list back in the bar and hides the button. The popover attribute is still there, so nothing changes in the markup and no link exists twice.

data-panel chooses the shape of the open list. sheet, the default, hangs under the bar at the full width. drawer slides in from the start edge as a column with the page dimmed behind it. screen fills the viewport with the links large and centred. Where anchor positioning exists the sheet sits exactly under the bar; elsewhere it starts at the top of the viewport and covers the bar, and Escape or a click outside still closes it. A close item, an li carrying data-close with a button that hides the popover, sits in the panel's top end corner and disappears in the bar; screen needs one, since there is no outside to click.

<nav class="nav" aria-label="Site" data-threshold="sm" data-panel="drawer">
    <a href="#" data-brand>Yeti</a>
    <button type="button" popovertarget="menu" aria-label="Menu"><svg aria-hidden="true" viewBox="0 0 16 16"><path d="M2 4h12M2 8h12M2 12h12" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg></button>
    <ul id="menu" popover role="list">
        <li data-close><button type="button" popovertarget="menu" popovertargetaction="hide" aria-label="Close">×</button></li>
        <li><a href="#">Docs</a></li>
        <li><a href="#">Blog</a></li>
    </ul>
</nav>

A panel that is open stays a panel until it is dismissed, whatever the nav's width; widening past the threshold does not return the links to the bar on its own. Escape or a click outside closes the panel, and only then do the links return to the bar. data-threshold is the attribute columns uses, so a nav and the columns under it can switch at the same width.

A nav item may hold a dropdown for a section with children of its own: the item becomes a button rather than a link, since the dropdown brings its own trigger. Its panel is in the top layer, so the bar's own containment cannot clip it.

The panel changes shape with the nav. In the bar it is the dropdown's usual card, anchored under its trigger. Once the links are behind the toggle it becomes a full-width block instead, docked to the foot of its trigger and squared off against the panel's own edges, so a sheet, a drawer and a screen each keep their submenu inside them. It is still a popover, so it covers the items below rather than pushing them down, and Escape or a click outside closes it before the panel it sits in. Where anchor positioning is missing the block docks to the foot of the viewport, which is still full width and still within reach.

Accessibility

Give the nav an aria-label, since a page often has more than one. The toggle needs a name, from aria-label or visible text; the browser sets its expanded state. Mark the current page's link with aria-current="page". Put role="list" on the ul: Yeti's reset only removes list markers where that role says the list is decorative. The open panel follows the toggle in the tab order, so Tab from the toggle reaches the first link; Escape closes the panel and returns focus to the toggle.

Attributes

Attribute Type Values Default Description
data-threshold enum 2xs, xs, sm, md, lg, xl, 2xl md The nav's own width at or above which the links sit in the bar; below it they are behind the toggle.
data-panel enum sheet, drawer, screen sheet The shape of the open list below the threshold: a sheet under the bar, a drawer from the start edge, or the whole screen.
data-variant enum primary, secondary, success, warning, alert, neutral primary The hue of the current link and of the hover tint.
data-gap enum none, xs, sm, md, lg, xl, 2xl, 3xl, xs-sm, xs-md, xs-lg, xs-xl, xs-2xl, xs-3xl, sm-md, sm-lg, sm-xl, sm-2xl, sm-3xl, md-lg, md-xl, md-2xl, md-3xl, lg-xl, lg-2xl, lg-3xl, xl-2xl, xl-3xl, 2xl-3xl sm Space between the bar's parts and between the links.

Markers

Attributes that descendants carry, not the root.

Attribute Type Values On Description
data-brand boolean > * The site's name or mark, usually a link home.
data-close boolean li The item holding the button that closes the panel; hidden in the bar.
data-actions boolean > * Buttons at the end of the bar.

Children

  • > [data-brand]: 0 to 1. The site's name or mark, usually a link home.
  • > button[popovertarget]: exactly 1. The toggle; its popovertarget names the list's id. Hidden while the links are in the bar.
  • > ul[popover]: exactly 1. The links, one per li, with role="list" and the id the toggle names.
  • li: at least 1. An entry in the list; each holds one link.
  • [data-close]: 0 to 1. An li holding a button with popovertargetaction="hide", shown at the panel's top end corner and hidden in the bar. Required with data-panel="screen".
  • .dropdown: any number. A nav item may hold a dropdown for its own children; the panel is in the top layer, so the bar never clips it. In the bar it is the usual card; behind the toggle it becomes a full-width block docked under its trigger and squared off against the open panel's edges.
  • > [data-actions]: 0 to 1. Buttons at the end of the bar.

Tokens

Token Description
--yeti-nav-padding Padding of the bar and of the open panel.
--yeti-nav-radius Corner of the links and the toggle.
--yeti-nav-border The bar's bottom edge, and the sheet's.
--yeti-nav-surface Background of the bar.
--yeti-nav-panel Background of the open panel.
--yeti-control-size Minimum height of the bar, the toggle, and each link.
--yeti-shadow-md The open panel's shadow.
--yeti-shadow-color The drawer's backdrop.
--yeti-color-text The bar's text, and the open panel's.
--yeti-border-width Width of the bar's bottom edge, and of the sheet's.
--yeti-space-sm The default gap; a link's inline padding and the close item's inset follow it.
--yeti-color-primary The default variant's colour, when data-variant is absent.
--yeti-color-primary-subtle The default variant's tint.
--yeti-color-primary-soft The default variant's soft stop.
--yeti-color-primary-strong The default variant's strong stop.
--yeti-color-primary-text The default variant's text colour.
--yeti-on-primary Text on the default variant's colour.
--yeti-weight-strong Weight of the brand, and of the current page's link.
--yeti-space-xs Block padding of a link, and of the close button.
--yeti-duration-fast How long the panel takes to open.
--yeti-ease The curve of that transition.
--yeti-text-lg Text size of the links in a screen panel.
--yeti-space-xl Inset of a screen panel that has no close item.
Internal tokens (may change between minor versions) - `--_yeti-gap` - `--_yeti-variant` - `--_yeti-variant-subtle` - `--_yeti-variant-text` - `--_yeti-variant-soft` - `--_yeti-variant-strong` - `--_yeti-on-variant`

Accessibility

  • Required attributes: aria-label or aria-labelledby
  • Give the nav an aria-label, since a page often has more than one. The toggle needs a name (aria-label or visible text); the browser sets its expanded state. Mark the current page's link with aria-current="page". Put role="list" on the ul, because the reset only removes list markers where that role says the list is decorative. With data-panel="screen" there is no outside to click, so include the close item.
Key Action
Enter / Space On the toggle, opens or closes the list.
Tab From the toggle, into the open list's first link.
Escape Closes the open list and returns focus to the toggle.

Browser support

  • Used without guards: popover, container size queries, @starting-style
  • Behind @supports: anchor positioning with anchor-scope (fallback: the sheet starts at the top of the viewport, and a submenu in the open panel docks to the foot of it)

JavaScript

None. This component is CSS only.

Available since 7.0.0.