Skip to content

a11y: narrow-viewport sidebar toolbar is a nav landmark with no links #14868

Description

@cwickham

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:
    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: false there are no links at all. The <a> at L74 and L78
    has no href. It is a Bootstrap collapse trigger, and Make website sidebar section toggles keyboard-operable buttons #14826 already removed
    the role="navigation" and role="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:

@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 selector nav.quarto-secondary-nav and need updating.
  • With breadcrumbs: false the bar then holds no landmark at all. Make sure the
    page title inside it is still reachable.
  • The navigation-toolbar-label language key added by Give every website and book nav landmark a distinct, localizable label #14813 becomes unused. It
    ships in _language.yml and 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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    accessibilitybugSomething isn't workingthemesRelated to HTML theming or any other style related issue (like highlight-style)

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions