Skip to content

Commit 5f85ecc

Browse files
authored
Give every website and book nav landmark a distinct, localizable label (#14813)
Website and book pages emit up to six <nav> landmarks. Before this change, only the breadcrumbs had a label, and it was hardcoded English ("breadcrumb"). Assistive technology listed the rest as identical "navigation" entries, and axe flagged the pages under landmark-unique. Each landmark now gets a distinct label from a new set of language keys, all nouns so they hold up under translation (bare adjectives like "Main" don't survive into languages where the word is a bound prefix or needs to agree with a noun): - Navbar: navigation-site-label ("Site") - Sidebar: adaptive — "Site" with no navbar, navigation-section-label ("Section") when a navbar is also present - Mobile nav strip: navigation-toolbar-label ("Toolbar") - Previous/next page nav: navigation-page-label ("Page") - Breadcrumbs (both copies): navigation-breadcrumbs-label ("Breadcrumbs") - Table of contents (html and revealjs): aria-labelledby pointing at its existing localized heading, no new key All keys are overridable per-document or site-wide via `language:` metadata, and translated across all 33 bundled language files. Verified axe landmark-unique passes across navbar/sidebar/book combinations and viewport sizes, and confirmed with the VoiceOver landmark rotor that each landmark announces a distinct name. Fixes #14376.
1 parent f0eba45 commit 5f85ecc

60 files changed

Lines changed: 633 additions & 19 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎news/changelog-1.11.md‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ All changes included in 1.11:
1212
- ([#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-role-mismatch`).
1313
- ([#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 they are keyboard accessible and properly announced.
1414
- ([#14774](https://github.com/quarto-dev/quarto-cli/issues/14774)): Fix missing keyboard focus indicator on the code tools button and on a website's sidebar toggle and sidebar search buttons.
15+
- ([#14376](https://github.com/quarto-dev/quarto-cli/issues/14376)): Add a distinct, localizable `aria-label` to each navigation landmark of websites and books: the navbar (`Site`), the sidebar (`Section`, or `Site` when there is no navbar), the narrow-viewport toolbar (`Toolbar`), the previous/next page navigation (`Page`), and the breadcrumbs (`Breadcrumbs`) (previously hardcoded English `breadcrumb`). The new `navigation-*-label` language keys can be overridden with `language:` metadata.
16+
- ([#14376](https://github.com/quarto-dev/quarto-cli/issues/14376)): Translate the new `navigation-*-label` keys in all 33 built-in language files. The values are drawn from existing human-translated interface strings (LibreOffice, GNOME, Wikidata) and each one carries a comment naming its source; values that were adapted rather than used verbatim are marked `needs review`.
17+
- ([#14376](https://github.com/quarto-dev/quarto-cli/issues/14376)): Label the table of contents `<nav>` with its localized title (`aria-labelledby`), in `html` and `revealjs` output, so assistive technology can tell it apart from other navigation landmarks.
1518

1619
## Formats
1720

‎src/config/constants.ts‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,12 @@ export const kToggleDarkMode = "toggle-dark-mode";
305305
export const kToggleReaderMode = "toggle-reader-mode";
306306
export const kToggleNavigation = "toggle-navigation";
307307

308+
export const kNavigationSiteLabel = "navigation-site-label";
309+
export const kNavigationSectionLabel = "navigation-section-label";
310+
export const kNavigationToolbarLabel = "navigation-toolbar-label";
311+
export const kNavigationPageLabel = "navigation-page-label";
312+
export const kNavigationBreadcrumbsLabel = "navigation-breadcrumbs-label";
313+
308314
export const kCopyButtonTooltip = "copy-button-tooltip";
309315
export const kCopyButtonTooltipSuccess = "copy-button-tooltip-success";
310316
export const kBackToTop = "back-to-top";
@@ -434,6 +440,11 @@ export const kLanguageDefaultsKeys = [
434440
kToggleReaderMode,
435441
kToggleSidebar,
436442
kToggleSection,
443+
kNavigationSiteLabel,
444+
kNavigationSectionLabel,
445+
kNavigationToolbarLabel,
446+
kNavigationPageLabel,
447+
kNavigationBreadcrumbsLabel,
437448
kCopyButtonTooltip,
438449
kCopyButtonTooltipSuccess,
439450
kBackToTop,

‎src/config/types.ts‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,11 @@ import {
157157
kMathml,
158158
kMergeIncludes,
159159
kMermaidFormat,
160+
kNavigationBreadcrumbsLabel,
161+
kNavigationSiteLabel,
162+
kNavigationPageLabel,
163+
kNavigationToolbarLabel,
164+
kNavigationSectionLabel,
160165
kNotebookLinks,
161166
kNotebookPreserveCells,
162167
kNotebookPreviewBack,
@@ -703,6 +708,11 @@ export interface FormatLanguage {
703708
[kToggleReaderMode]?: string;
704709
[kToggleSection]?: string;
705710
[kToggleSidebar]?: string;
711+
[kNavigationSiteLabel]?: string;
712+
[kNavigationSectionLabel]?: string;
713+
[kNavigationToolbarLabel]?: string;
714+
[kNavigationPageLabel]?: string;
715+
[kNavigationBreadcrumbsLabel]?: string;
706716
[kSearchMatchingDocumentsText]?: string;
707717
[kSearchCopyLinkTitle]?: string;
708718
[kSearchMoreMatchText]?: string;

‎src/project/types/website/website-navigation.ts‎

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ import {
130130
import {
131131
kBackToTop,
132132
kIncludeInHeader,
133+
kNavigationBreadcrumbsLabel,
133134
kNumberSections,
134135
kRepoActionLinksEdit,
135136
kRepoActionLinksIssue,
@@ -472,7 +473,7 @@ function navigationHtmlPostprocessor(
472473
);
473474
if (secondaryNavTitleEl) {
474475
if (showBreadCrumbs) {
475-
const navEl = makeBreadCrumbs(doc);
476+
const navEl = makeBreadCrumbs(doc, language);
476477
if (secondaryNavTitleEl.parentElement) {
477478
secondaryNavTitleEl.parentElement.replaceChild(
478479
navEl,
@@ -506,6 +507,7 @@ function navigationHtmlPostprocessor(
506507
if (navigation.breadCrumbs && navigation.breadCrumbs.length > 1) {
507508
const titleBreadCrumbEl = makeBreadCrumbs(
508509
doc,
510+
language,
509511
["quarto-title-breadcrumbs", "d-none", "d-lg-block"],
510512
);
511513
// See if there is deeper target
@@ -907,7 +909,11 @@ async function resolveFooter(
907909
return footer;
908910
}
909911

910-
function makeBreadCrumbs(doc: Document, clz?: string[]) {
912+
function makeBreadCrumbs(
913+
doc: Document,
914+
language: FormatLanguage,
915+
clz?: string[],
916+
) {
911917
// Make bootstrap breadcrumbs
912918
const navEl = doc.createElement("nav");
913919
navEl.classList.add("quarto-page-breadcrumbs");
@@ -916,7 +922,7 @@ function makeBreadCrumbs(doc: Document, clz?: string[]) {
916922
navEl.classList.add(cls);
917923
});
918924
}
919-
navEl.setAttribute("aria-label", "breadcrumb");
925+
navEl.setAttribute("aria-label", language[kNavigationBreadcrumbsLabel]!);
920926

921927
const olEl = doc.createElement("ol");
922928
olEl.classList.add("breadcrumb");

‎src/resources/editor/tools/vs-code.mjs‎

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10596,6 +10596,11 @@ var require_yaml_intelligence_resources = __commonJS({
1059610596
null
1059710597
]
1059810598
},
10599+
"navigation-site-label": "string",
10600+
"navigation-section-label": "string",
10601+
"navigation-toolbar-label": "string",
10602+
"navigation-page-label": "string",
10603+
"navigation-breadcrumbs-label": "string",
1059910604
"crossref-fig-title": "string",
1060010605
"crossref-tbl-title": "string",
1060110606
"crossref-lst-title": "string",
@@ -25884,12 +25889,12 @@ var require_yaml_intelligence_resources = __commonJS({
2588425889
mermaid: "%%"
2588525890
},
2588625891
"handlers/mermaid/schema.yml": {
25887-
_internalId: 218590,
25892+
_internalId: 218600,
2588825893
type: "object",
2588925894
description: "be an object",
2589025895
properties: {
2589125896
"mermaid-format": {
25892-
_internalId: 218582,
25897+
_internalId: 218592,
2589325898
type: "enum",
2589425899
enum: [
2589525900
"png",
@@ -25905,7 +25910,7 @@ var require_yaml_intelligence_resources = __commonJS({
2590525910
exhaustiveCompletions: true
2590625911
},
2590725912
theme: {
25908-
_internalId: 218589,
25913+
_internalId: 218599,
2590925914
type: "anyOf",
2591025915
anyOf: [
2591125916
{

‎src/resources/editor/tools/yaml/all-schema-definitions.json‎

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

‎src/resources/editor/tools/yaml/web-worker.js‎

Lines changed: 8 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎src/resources/editor/tools/yaml/yaml-intelligence-resources.json‎

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3564,6 +3564,11 @@
35643564
null
35653565
]
35663566
},
3567+
"navigation-site-label": "string",
3568+
"navigation-section-label": "string",
3569+
"navigation-toolbar-label": "string",
3570+
"navigation-page-label": "string",
3571+
"navigation-breadcrumbs-label": "string",
35673572
"crossref-fig-title": "string",
35683573
"crossref-tbl-title": "string",
35693574
"crossref-lst-title": "string",
@@ -18852,12 +18857,12 @@
1885218857
"mermaid": "%%"
1885318858
},
1885418859
"handlers/mermaid/schema.yml": {
18855-
"_internalId": 218590,
18860+
"_internalId": 218600,
1885618861
"type": "object",
1885718862
"description": "be an object",
1885818863
"properties": {
1885918864
"mermaid-format": {
18860-
"_internalId": 218582,
18865+
"_internalId": 218592,
1886118866
"type": "enum",
1886218867
"enum": [
1886318868
"png",
@@ -18873,7 +18878,7 @@
1887318878
"exhaustiveCompletions": true
1887418879
},
1887518880
"theme": {
18876-
"_internalId": 218589,
18881+
"_internalId": 218599,
1887718882
"type": "anyOf",
1887818883
"anyOf": [
1887918884
{

‎src/resources/formats/html/pandoc/toc.html‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<nav id="$idprefix$TOC" role="doc-toc">
1+
<nav id="$idprefix$TOC" role="doc-toc"$if(toc-title)$ aria-labelledby="$idprefix$toc-title"$endif$>
22
$if(toc-title)$
33
<h2 id="$idprefix$toc-title">$toc-title$</h2>
44
$endif$

‎src/resources/formats/revealjs/pandoc/toc-slide.html‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<section id="$idprefix$TOC">
2-
<nav role="doc-toc">
2+
<nav role="doc-toc"$if(toc-title)$ aria-labelledby="$idprefix$toc-title"$endif$>
33
$if(toc-title)$
44
<h2 id="$idprefix$toc-title">$toc-title$</h2>
55
$endif$

0 commit comments

Comments
 (0)