Link + Popover Navigation
This is a redress of my 2019 post Link + Disclosure Widget Navigation, except (as the title implies), I’ve modified it to use native HTML popovers instead of ARIA or HTML disclosure widgets.
Popover has the benefit of using appropriate HTML structure and semantics while removing the need for scripting and ARIA. I use some ARIA here regardless.
popover vs. Disclosure Widgets
ARIA-based disclosure widgets (<button aria-expanded>) require script to work. HTML-based disclosure widgets (<details> / <summary>) expand when there is a hit for in-page searches (in two engines), have inconsistent exposure to accessibility APIs (across browsers and over time), and in my experience confuddle teams with styling overrides. I have a 2020 post going into some detail on each.
The popover feature requires CSS for positioning. Cross-browser anchor positioning support only came about recently. Because popovers appear on the top layer, you have to be careful with anything that uses <dialog> element, otherwise overlapping issues happen.
The Pattern
Embedded below or visit it directly at Codepen. There’s also a debug version.
See the Pen Link + Popover Nav by Adrian Roselli (@aardrian) on CodePen.
The Container
The navigation lives in a <nav> as a list with nested lists.
<header>
<nav id="Nav">
<ul>
[…]
</ul>
</nav>
</header>
The Links
The links are links. They work with a keyboard, assistive technology knows what to do with them, and links are generally well understood by users.
I identify that the current parent page is the About page and the current page is Job Postings by using aria-current="page". I also use that as my CSS selector and you should too.
<li>
<a href="[…]" id="Item02" aria-current="page">About</a>
[…]
<ul id="SubItem02" popover>
[…]
<li><a href="[…]" aria-current="page">Job Postings</a></li>
</ul>
</li>
The Button Trigger
As my older pattern, the <button> element is the trigger. Unlike my older pattern, it doesn’t use aria-expanded.
Instead it uses popovertarget, which brings the programmatic state aria-expanded for free. The value of popovertarget must be the id of the content whose appearance you want to control (the popover content). The popover content needs the popover attribute for this to work.
No CSS or JavaScript is needed to make this content appear or disappear.
<button type="button" id="btnItem02" aria-labelledby="Item02" popovertarget="SubItem02">
<svg […]>[…]</svg>
</button>
<ul id="SubItem02" popover>
[…]
</ul>
Naming the Button
This is the second place I still use ARIA. To avoid text duplication and auto-translation issues, I use aria-labelledby to reference the text of the preceding link. Screen reader users hear / feel a different control type and that it’s expandable, which has proven to be sufficient context. For voice users, saying the visible text presents them with the option to choose the link or the button.
<a href="[…]" id="Item02" aria-current="page">About</a>
<button type="button" id="btnItem02" aria-labelledby="Item02" popovertarget="SubItem02">
<svg […]>[…]</svg>
</button>
Focus Order Support
You must place the popover content immediately after the popover trigger, otherwise you risk reading order and focus order problems for users. The popover feature does not automatically do any focus management.
Esc Support
It comes for free with popover.
Click-to-Close Support
It comes for free with popover.
Focusout-to-Close Support
You’ll have to write your own.
Styles
Not all of these styles will work for you, but these may matter:
- I use logical properties throughout. The demo has buttons to let you cycle between four writing directions. They are not a perfect test since neither the rest of the page nor the text change.
- There are no classes. IDs only exist as hooks for popover and naming references.
- the links are block links (using CSS generated content) to fill the container. The buttons have a
z-indexto sit in front of them. - The layout uses flexbox with no width media queries. If you don’t like the wrap or the layout, write your own.
- Supports forced-colors / Windows High Contrast Mode out of the box with no custom CSS. That’s partly because the focus styles use outlines.
- Uses
color-scheme: light darkjust so I could more easily see styling gaps, but I don’t recommend that for real-life use. - The buttons use an arrow SVG that rotates via
:popover-openwhen the popover appears / disappears. It honors user motion preferences. - The button and arrow position shift based on typeface, as with any layout mixing icons and type.
- The popover triggers conform to 2.5.8 Target Size (Minimum), but you can always make them bigger. Make sure yours at least conform.
There is a button on the demo to remove all styles from the page, making it easier to see how this will perform as raw HTML, for easy testing, or for CSS Naked Day.
Popover Positioning
I haven’t played with popovers much. Positioning it to work across Firefox, Chromium, and Safari while also not overflowing the window or falling apart with text direction changes proved to be a challenge. I welcome suggestions on improvements.
My goal was to have the popover appear to the right edge of the parent list item and just below the button — for left-to-right languages — and in a corresponding location for other language text directions.
[popover] {
inset: auto;
position-anchor: auto;
inset-block-start: anchor(self-end);
inset-inline-end: calc(anchor(self-end) - 1em);
}
I flailed about trying different values from the spec and struggled to understand logical properties with anchor positioning. As it is, the popover positioning falls apart with the vertical writing modes, but I can’t be sure if that’s my code or browser bugs (¿Por qué no los dos?).
Pattern History
There are reasons I built this pattern.
Early in 2017 I filed an issue against ARIA Authoring Practices (APG) requesting a change to the menu navigation pattern. Despite a great deal of feedback in agreement, it continues to languish. In late 2017 I wrote Don’t Use ARIA Menu Roles for Site Nav and started actively campaigning against the APG pattern. In 2019, Sarah Higley proposed a disclosure-only APG pattern and then a disclosure and link pattern proposal in 2020.
HTML (via Open-UI) has since stepped in to fill some gaps, so here we are.
Wrap Up
Standard disclaimers apply: may not work for your audience, likely has bugs, support can change, new features may moot this, my design sense is poor, yes you have to write script to get whatever custom interactions you think are nifty but which probably annoy users, who even writes code anymore, using [insert random element here] in a way that’s not mentioned in this post is your problem not mine, etc.
And if you have more experience with multilingual popover anchor positioning, please share!
Leave a Comment or Response