Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions news/changelog-1.11.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ All changes included in 1.11:
## Accessibility

- ([#14615](https://github.com/quarto-dev/quarto-cli/issues/14615)): Fix invalid `role="menu"` on the website navbar's collapse toggle button, flagged by axe-core (`aria-allowed-role`) and WAVE (`aria_menu_broken`) when the navbar collapses to the hamburger at narrow viewports.
- ([#12116](https://github.com/quarto-dev/quarto-cli/issues/12116), [#4935](https://github.com/quarto-dev/quarto-cli/issues/4935)): Website sidebar section toggles are now `<button>` elements, so the keyboard can reach them and `Enter` or `Space` can operate them. Each toggle carries `aria-expanded`, and `aria-labelledby` names each disclosed list after its own section. The toggles no longer carry `role="navigation"`, which removed a spurious navigation landmark from every section.

## Engines

Expand Down
42 changes: 42 additions & 0 deletions src/resources/projects/website/navigation/quarto-nav.scss
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,48 @@ $sidebar-section-bottom-margin: 0.2em;
cursor: pointer;
}

// Section disclosure controls are <button> elements so that they are reachable
// by keyboard and expose the button role (quarto-cli#12116, quarto-cli#4935).
// Strip the browser's default button styling so they still look like the
// sidebar links beside them.
.sidebar-item .sidebar-item-toggle,
.sidebar-item button.sidebar-item-text {
background: none;
border: none;
padding: 0;
font: inherit;
color: inherit;
text-align: inherit;
}

// In a section without its own page, the label and the chevron are one control,
// so that button has to lay out its own children instead of relying on the
// flex container. `margin-left: auto` keeps the chevron hard right whether or
// not the item also has a navicon.
//
// `align-items: baseline` is deliberate, not a typo for `center`: it reproduces
// the line box of the inline `<a>` this button replaced, so row heights and the
// chevron position are unchanged from 1.10. `center` renders 1-2px shorter per
// section, which shifts every row below it.
.sidebar-item button.sidebar-item-text {
display: flex;
align-items: baseline;
width: 100%;
}

.sidebar-item button.sidebar-item-text > .bi-chevron-right {
margin-left: auto;
padding-left: 0.5rem;
}

// A bare button gets no focus ring from Bootstrap's reboot, so state one
// explicitly rather than depend on the UA default (quarto-cli#14774).
.sidebar-item .sidebar-item-toggle:focus-visible,
.sidebar-item button.sidebar-item-text:focus-visible {
outline: 2px solid currentColor;
outline-offset: 2px;
}

.sidebar-item .sidebar-item-toggle .bi {
// The dongle for opening and closing sections
font-size: 0.7rem;
Expand Down
6 changes: 2 additions & 4 deletions src/resources/projects/website/templates/nav-before-body.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,11 @@ const navbarTocRight = nav['toc-location'] === "right" || nav['toc-location'] ==
</button>
<% if (nav.showBreadCrumbs) { %>
<h1 class="quarto-secondary-nav-title no-breadcrumbs"></h1>
<a class="flex-grow-1" role="navigation" data-bs-toggle="collapse" data-bs-target=".quarto-sidebar-collapse-item" role="link"
aria-controls="quarto-sidebar" aria-expanded="false" aria-label="<%- nav.language['toggle-sidebar'] %>"
<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" role="navigation" data-bs-toggle="collapse" data-bs-target=".quarto-sidebar-collapse-item" role="link"
aria-controls="quarto-sidebar" aria-expanded="false" aria-label="<%- nav.language['toggle-sidebar'] %>"
<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>
Expand Down
12 changes: 6 additions & 6 deletions src/resources/projects/website/templates/sidebaritem.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,15 @@
<% if (item.contents.length > 0) { %>
<div class="sidebar-item-container">
<% if (item.href) { %>
<a href="<%- item.href %>" class="sidebar-item-text sidebar-link<%- item.active ? " active" : "" %>"<%= item.rel ? ` rel="${item.rel}"` : "" %><%= item.target ? ` target="${item.target}"` : "" %>><% partial('navicon.ejs', { item }) %> <span class="menu-text"><%= item.text %></span></a>
<a href="<%- item.href %>" class="sidebar-item-text sidebar-link<%- item.active ? " active" : "" %>"<%= item.rel ? ` rel="${item.rel}"` : "" %><%= item.target ? ` target="${item.target}"` : "" %>><% partial('navicon.ejs', { item }) %> <span class="menu-text"<%= sectionId ? ` id="${sectionId}-label"` : "" %>><%= item.text %></span></a>
<button type="button" class="sidebar-item-toggle text-start<%- isCollapsed ? " collapsed" : "" %>" data-bs-toggle="collapse" data-bs-target="#<%- sectionId %>"<%= sectionId ? ` aria-controls="${sectionId}"` : "" %> aria-expanded="<%- isCollapsed ? "false" : "true" %>" aria-label="<%- language['toggle-section'] %>: <%- item.text %>">
<i class="bi bi-chevron-right ms-2" aria-hidden="true"></i>
</button>
<% } else { %>
<a class="sidebar-item-text sidebar-link text-start<%- isCollapsed ? " collapsed" : "" %>" data-bs-toggle="collapse" data-bs-target="#<%- sectionId %>" role="navigation" aria-expanded="<%- isCollapsed ? "false" : "true" %>"><% partial('navicon.ejs', { item }) %> <span class="menu-text"><%= item.text %></span></a>
<button type="button" class="sidebar-item-text sidebar-item-toggle sidebar-link text-start<%- isCollapsed ? " collapsed" : "" %>" data-bs-toggle="collapse" data-bs-target="#<%- sectionId %>"<%= sectionId ? ` aria-controls="${sectionId}"` : "" %> aria-expanded="<%- isCollapsed ? "false" : "true" %>"><% partial('navicon.ejs', { item }) %> <span class="menu-text"<%= sectionId ? ` id="${sectionId}-label"` : "" %>><%= item.text %></span><i class="bi bi-chevron-right" aria-hidden="true"></i></button>
<% } %>
<a class="sidebar-item-toggle text-start<%- isCollapsed ? " collapsed" : "" %>" data-bs-toggle="collapse" data-bs-target="#<%- sectionId %>" role="navigation" aria-expanded="<%- isCollapsed ? "false" : "true" %>" aria-label="<%- language['toggle-section'] %>">
<i class="bi bi-chevron-right ms-2"></i>
</a>
</div>
<ul id="<%- sectionId %>" class="collapse list-unstyled sidebar-section depth<%-depth%> <%- isCollapsed ? "" : "show" %>">
<ul id="<%- sectionId %>"<%= sectionId ? ` aria-labelledby="${sectionId}-label"` : "" %> class="collapse list-unstyled sidebar-section depth<%-depth%> <%- isCollapsed ? "" : "show" %>">
<% item.contents.forEach(subItem => { %>
<% partial('sidebaritem.ejs', { item: subItem, depth: depth + 1, collapse: collapse, borderColor: borderColor, language: language, draftMode }) %>
<% }) %>
Expand Down
2 changes: 2 additions & 0 deletions tests/docs/smoke-all/website/sidebar-disclosure/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/.quarto/
**/*.quarto_ipynb
25 changes: 25 additions & 0 deletions tests/docs/smoke-all/website/sidebar-disclosure/_quarto.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
project:
type: website
output-dir: .

website:
title: "Sidebar disclosure"
sidebar:
style: docked
collapse-level: 1
contents:
- index.qmd
- section: "Group"
contents:
- group-a.qmd
- group-b.qmd
- section: "Nested"
contents:
- nested.qmd
- section: section.qmd
contents:
- section-child.qmd

format:
html:
theme: cosmo
15 changes: 15 additions & 0 deletions tests/docs/smoke-all/website/sidebar-disclosure/group-a.qmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
title: "Group A"
_quarto:
tests:
html:
ensureHtmlElements:
- ['button.sidebar-item-toggle[data-bs-target="#quarto-sidebar-section-1"][aria-expanded="true"]',
'ul#quarto-sidebar-section-1.show[aria-labelledby="quarto-sidebar-section-1-label"]',
'button.sidebar-item-toggle[data-bs-target="#quarto-sidebar-section-2"][aria-expanded="false"]']
- ['#quarto-sidebar a.sidebar-item-toggle',
'#quarto-sidebar a[role="navigation"]']
---

The section holding the active page reports `aria-expanded="true"`, while a
nested section that stays closed still reports `false`.
5 changes: 5 additions & 0 deletions tests/docs/smoke-all/website/sidebar-disclosure/group-b.qmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
title: "group-b"
---

Page group-b.
23 changes: 23 additions & 0 deletions tests/docs/smoke-all/website/sidebar-disclosure/index.qmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
title: "Index"
_quarto:
tests:
html:
ensureHtmlElements:
- ['button.sidebar-item-text.sidebar-item-toggle[data-bs-toggle="collapse"][data-bs-target="#quarto-sidebar-section-1"][aria-controls="quarto-sidebar-section-1"][aria-expanded="false"]',
'button.sidebar-item-text.sidebar-item-toggle span.menu-text#quarto-sidebar-section-1-label',
'ul#quarto-sidebar-section-1[aria-labelledby="quarto-sidebar-section-1-label"]',
'button.sidebar-item-text.sidebar-item-toggle[data-bs-target="#quarto-sidebar-section-2"][aria-expanded="false"]',
'ul#quarto-sidebar-section-2[aria-labelledby="quarto-sidebar-section-2-label"]',
'a.sidebar-item-text[href="./section.html"] span.menu-text#quarto-sidebar-section-3-label',
'button.sidebar-item-toggle[aria-label="Toggle section: section"][aria-expanded="false"][aria-controls="quarto-sidebar-section-3"]',
'ul#quarto-sidebar-section-3[aria-labelledby="quarto-sidebar-section-3-label"]']
- ['#quarto-sidebar a.sidebar-item-toggle',
'#quarto-sidebar a[data-bs-toggle="collapse"]',
'#quarto-sidebar a[role="navigation"]',
'#quarto-sidebar a[aria-expanded]']
---

Every sidebar section toggle is a `<button>` with `aria-expanded`, and every
disclosed list is named by its own section label. See quarto-cli#12116 and
quarto-cli#4935.
5 changes: 5 additions & 0 deletions tests/docs/smoke-all/website/sidebar-disclosure/nested.qmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
title: "nested"
---

Page nested.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
title: "section-child"
---

Page section-child.
5 changes: 5 additions & 0 deletions tests/docs/smoke-all/website/sidebar-disclosure/section.qmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
title: "section"
---

Page section.
Loading