Skip to main content

Catalog · Navigation

Submenu

Groups a few links of a header under one button, so the header keeps a short row of links.

Fits best: any site — when a header has more links than fit in one row

Example

A winery’s header with two submenus: Wines and About us.

Press Wines or About us. From 769 px of window width each opens a box under its button; at 768 px and below the links are in the menu, and each group opens in place, under its button.

Known issues

A submenu is a native <details>, which has no Escape key and does not close when you click elsewhere: it closes when you press its button again or open another one (in browsers that support name on <details>: Chrome and Edge 120, Safari 17.2, Firefox 130; in older ones several can be open at once). From 769 px an open box stays over the top of the page until then, and if you keep pressing Tab after it, a link of the page behind it can be hidden (WCAG 2.2 success criterion 2.4.11, Focus not obscured).

In the menu at 768 px and below this does not happen: the links are in the flow and Escape closes the whole menu. It has not been tested with a screen reader.

Code

Copy the markup

Bundler
import "@luz-ds/catalog/components/submenu.css";
HTML
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@luz-ds/catalog@0.3.0/dist/components/submenu.min.css" integrity="sha384-y+7suWYL2a3Jot2cD9bCKS/ollQ/VHJGqkHQNIHmAEnQh1TyfrypOHFyhNk/3x3H" crossorigin="anonymous">

An item of the list of the catalog’s navbar, in a header such as the site header or the masthead, whose content is a <luz-submenu> around a <details>: its <summary> is the button, and a list of links follows it. No class: the tag is the block, and its parts are found by their place.

Give every submenu of the header the same name, so opening one closes the others.

HTML
<nav class="luz-navbar" id="site-nav" aria-label="Main" popover>
  <ul class="luz-navbar__list">
    <li><a class="luz-navbar__link" href="/" aria-current="page">Home</a></li>
    <li>
      <luz-submenu>
        <details name="site-menu">
          <summary>Wines</summary>
          <ul>
            <li><a class="luz-navbar__link" href="/wines/red/">Red wines</a></li>
            <li><a class="luz-navbar__link" href="/wines/white/">White wines</a></li>
          </ul>
        </details>
      </luz-submenu>
    </li>
    <li><a class="luz-navbar__link" href="/visits/">Visits</a></li>
  </ul>
</nav>

Variants and options

There are no variants. The attributes are the browser’s.

Tag or attributeEffect
<luz-submenu>
Around the <details>, with display: contents: it draws no box and adds no role. The <summary> looks like the links beside it, with a chevron; the list of links is indented under it, and from 769 px a box under it
name
The same value on every submenu of the header: opening one closes the others. Browsers without it leave the others open
open
Set by the browser. Do not write it in the markup: a submenu open on every page hides the page under it

How it works

CSS from @luz-ds/catalog and a native <details>. No JavaScript: <luz-submenu> is a tag that no script ever defines.

  • The browser opens and closes it, and tells assistive technology whether it is open. A submenu opens on a click, a tap, Enter or Space: never on hover, so it does not open under a pointer passing by, and it works the same on a touch screen.
  • At 768 px and below, inside the header’s menu, the links of a group open in place, under their button, indented, and push the next links down.
  • From 769 px, where the header’s links are a row, they open in a box under their button, over the page; the page does not move. The box of the last item opens toward the start of the row, so it stays in the window, in either direction of text.
  • The chevron is drawn with two borders and has no text: the state is the one the browser exposes.
  • It does not use the popover API: a popover sits in the top layer, so it could not open in place inside the menu.

Without JavaScript

Nothing changes: there is no script. The links are plain links.

Keyboard and ARIA

  • Order: a closed submenu is one stop, its button. Open, its links come right after it, then the next item of the header.
  • Keys: Enter or Space on the button opens and closes it; focus stays on the button. Tab and Shift+Tab move through the links. Escape does nothing in a submenu (see Known issues); in the header’s menu at 768 px and below it closes the menu and returns focus to the menu button.
  • Names: the button’s name is its text. The browser exposes the expanded state of a <summary>: write no aria-expanded, no role="menu" and no role="menuitem"; these are links in a navigation landmark, not an application menu.
  • Current page: put aria-current="page" on the link of the page, in the submenu too.

Contrast and focus are in the Design tab.

Design

When to use

Use it when the header needs more links than fit in a short row, and some of them belong together: the kinds of wine, the pages about the company.

When not to use

Do not use it for a single link, or to hide pages most visitors need: put those in the row itself.

  • Do not nest a submenu in a submenu: two levels hide pages from the visitor and are hard to use with a pointer and a keyboard.
  • Do not put actions, forms or settings in it: it holds links to pages. For actions use buttons, or a dropdown.
  • If every group has many links, give each group a page of its own and link to it: a long submenu is a page in a box.

It replaces a dropdown navigation built with a script, and the hover menu. Nothing in LUZ is removed.

Do and don't

Do Name the group with a word that tells the visitor what is in it, and keep it to a handful of links.
Don't Use a vague group name such as “Resources” or “More”: the visitor has to open it to know whether what they want is there.

Writing the content

Name the group with one or two words in sentence case that say what it holds: “Wines”, “About us”.

  • Name each link after the page it opens, so it makes sense outside the group: “Red wines”, not “Red”.
  • Do not make the group’s name a page of its own as well: the button opens the group, it does not go anywhere. If the group has an overview page, make it the first link: “All wines”.
  • Keep the same groups, in the same order, on every page.

Contrast and focus

The button and the links in the row sit on the page; from 769 px the box is a raised surface with a border and a shadow.

PartColour on backgroundLightDarkHigh contrastNeeds
Button text and chevron
--luz-text-secondary on --luz-surface-page
6.2:1
7.5:1
19.3:1
4.5:1
Button, hovered or open
--luz-text-primary on --luz-surface-page
15.8:1
15.1:1
21.0:1
4.5:1
Link in the box, from 769 px
--luz-text-secondary on --luz-surface
6.4:1
6.9:1
19.3:1
4.5:1
Link in the box, hovered
--luz-text-primary on --luz-surface
16.5:1
14.0:1
21.0:1
4.5:1
Focus ring on the button
--luz-focus-ring on --luz-surface-page
5.6:1
7.7:1
21.0:1
3:1
Focus ring on a link in the box
--luz-focus-ring on --luz-surface
5.9:1
7.2:1
21.0:1
3:1

Measured from the token values of each mode, rounded down. Needs is the WCAG 2.2 AA minimum: 4.5:1 for text, 3:1 for focus rings. In the menu at 768 px and below, the links are on the menu’s page surface, as in the first two rows.

  • Focus: core’s 2 px --luz-focus-ring outline, 2 px outside the button and each link.
  • Open, the chevron points up as well as the button’s text getting darker: the state is never only a colour.
  • The current page’s link is underlined, as in the row.

Tokens it uses

The 14 tokens that the rules of catalog’s components/submenu.css read, by family. The links in it are the catalog’s navbar links. Each family links to its page, with the values in every mode. In Figma they are variables with the same names: see Tokens in Figma.

Spacing and sizing
--luz-control-lg, --luz-space-2xs, --luz-space-xs, --luz-space-md, --luz-space-xl
Typography
--luz-font-sans, --luz-text-md
Radius
--luz-radius-md
Shadows
--luz-shadow-md
Layers
--luz-z-overlay

Keyboard and ARIA are in the Code tab.