Repository navigation
a11y: narrow-viewport sidebar toolbar is a nav landmark with no links #14868
Description
The bar that appears below the navbar under the lg breakpoint is marked up as a
nav, so it becomes a navigation landmark. It holds the sidebar toggle button,
the page title or the breadcrumbs, and the search button. In neither
configuration does the nav itself hold a navigation link.
| <% if (nav.sidebar && nav.layout) { %> | |
| <nav class="quarto-secondary-nav"> | |
| <div class="container-fluid d-flex"> | |
| <button type="button" class="quarto-btn-toggle btn" | |
| data-bs-toggle="collapse" role="button" data-bs-target=".quarto-sidebar-collapse-item" | |
| aria-controls="quarto-sidebar" aria-expanded="false" aria-label="<%- nav.language['toggle-sidebar'] %>" | |
| onclick="if (window.quartoToggleHeadroom) { window.quartoToggleHeadroom(); }"> | |
| <i class="bi bi-layout-text-sidebar-reverse"></i> | |
| </button> | |
| <% if (nav.showBreadCrumbs) { %> | |
| <h1 class="quarto-secondary-nav-title no-breadcrumbs"></h1> | |
| <a class="flex-grow-1" data-bs-toggle="collapse" data-bs-target=".quarto-sidebar-collapse-item" | |
| onclick="if (window.quartoToggleHeadroom) { window.quartoToggleHeadroom(); }"> | |
| </a> | |
| <% } else { %> | |
| <a class="flex-grow-1 no-decor" data-bs-toggle="collapse" data-bs-target=".quarto-sidebar-collapse-item" | |
| onclick="if (window.quartoToggleHeadroom) { window.quartoToggleHeadroom(); }"> | |
| <h1 class="quarto-secondary-nav-title"></h1> | |
| </a> | |
| <% } %> | |
| <% if (nav.sidebar && nav.sidebar.search) { %> | |
| <button type="button" class="btn quarto-search-button" aria-label="<%- nav.language['search-label'] %>" onclick="window.quartoOpenSearch();"> | |
| <i class="bi bi-search"></i> | |
| </button> | |
| <% } %> | |
| </div> | |
| </nav> |
- With breadcrumbs on (the default), every link is inside a nested
nav.quarto-page-breadcrumbs, which is its own landmark. The postprocessor
swaps it in for the title element:
quarto-cli/src/project/types/website/website-navigation.ts
Lines 470 to 482 in 093316c
const secondaryNavTitleEl = doc.querySelector( ".quarto-secondary-nav .quarto-secondary-nav-title", ); if (secondaryNavTitleEl) { if (showBreadCrumbs) { const navEl = makeBreadCrumbs(doc); if (secondaryNavTitleEl.parentElement) { secondaryNavTitleEl.parentElement.replaceChild( navEl, secondaryNavTitleEl, ); } } else { - With
breadcrumbs: falsethere are no links at all. The<a>at L74 and L78
has nohref. It is a Bootstrap collapse trigger, and Make website sidebar section toggles keyboard-operable buttons #14826 already removed
therole="navigation"androle="link"it used to carry.
So the landmark holds a button, a heading, and another landmark.
The strip only exists below lg, and the sidebar it toggles is a collapse that
sits under it, so both are in the accessibility tree together when a reader opens
the sidebar:
quarto-cli/src/resources/projects/website/navigation/quarto-nav.scss
Lines 454 to 493 in 093316c
| @include media-breakpoint-down(lg) { | |
| .quarto-secondary-nav { | |
| display: block; | |
| button.quarto-search-button { | |
| padding-right: 0em; | |
| padding-left: 2em; | |
| } | |
| button.quarto-btn-toggle { | |
| margin-left: -0.75rem; | |
| margin-right: 0.15rem; | |
| } | |
| nav.quarto-title-breadcrumbs { | |
| display: none; | |
| } | |
| nav.quarto-page-breadcrumbs { | |
| display: flex; | |
| align-items: center; | |
| padding-right: 1em; | |
| margin-left: -0.25em; | |
| a { | |
| text-decoration: none; | |
| } | |
| ol.breadcrumb { | |
| margin-bottom: 0; | |
| } | |
| } | |
| } | |
| } | |
| @include media-breakpoint-up(lg) { | |
| .quarto-secondary-nav { | |
| display: none; | |
| } | |
| } |
This surfaced in review of #14813, which gives every nav landmark a distinct
aria-label. Naming this one is hard because there is no honest navigation word
for it. The label there is Toolbar, which a screen reader announces as
"Toolbar, navigation".
axe does not flag this. landmark-unique only requires that the labels differ,
which #14813 satisfies. This is a markup question, not a rule violation.
Suggested fix
Change the nav to a div and drop its aria-label. The nested breadcrumbs
nav keeps its own landmark and label, so the only difference for a reader is
that the Toolbar entry drops out of the landmark list. Nothing else moves, and
no links become unreachable.
Points to check:
- Most CSS selectors are class based (
.quarto-secondary-nav), but four rules
use the element selectornav.quarto-secondary-navand need updating. - With
breadcrumbs: falsethe bar then holds no landmark at all. Make sure the
page title inside it is still reachable. - The
navigation-toolbar-labellanguage key added by Give every website and booknavlandmark a distinct, localizable label #14813 becomes unused. It
ships in_language.ymland in all 33 translated files, so remove it before
1.11 is released rather than after.
Split from #14813. Part of #8706.
Investigated with AI (Claude Code), grounded in a local clone.
Activity
Julis-lab commentedon Sep 29, 2026
Hi :) I'd like to work on this issue.
I'll start by reproducing the current behavior, then update the secondary navigation markup and the affected SCSS selectors, and verify both breadcrumb and non-breadcrumb cases. I'll also check the related localization key and add/update regression coverage where appropriate.