The documented way to declare navbar and sidebar tools uses icon: alone, with no text:. That renders links with an empty accessible name, which axe-core reports as link-name.
Taking the example straight from https://quarto.org/docs/websites/website-navigation.html#navbar-tools:
project:
type: website
website:
title: "Tools repro"
navbar:
left:
- href: index.qmd
text: Home
tools:
- icon: bluesky
href: https://bsky.app
- icon: github
menu:
- text: Source Code
href: https://code.com
- text: Report a Bug
href: https://bugs.com
format:
html:
axe:
output: json
produces
<a href="https://bsky.app" title="" class="quarto-navigation-tool px-1" aria-label=""><i class="bi bi-bluesky"></i></a>
<a href="" title="" id="quarto-navigation-tool-dropdown-0" class="quarto-navigation-tool dropdown-toggle px-1" data-bs-toggle="dropdown" aria-expanded="false" role="link" aria-label=""><i class="bi bi-github"></i></a>
The icon is drawn by a class on an empty <i>, so there is no text node to fall back on. Chrome's accessibility tree announces both as link "", and axe-core reports link-name (serious, wcag2a / wcag244 / wcag412) on three nodes for that config: the navbar tool, the navbar dropdown tool, and the sidebar tool. Sidebar tools go through the same template, so website: sidebar: tools: is affected the same way, and the sidebar example in the docs has the same shape.
aria-label="" is worse than no attribute at all, since an empty aria-label blanks the name rather than letting anything else supply one. Both attributes are interpolated from tool.text, which is unset here:
|
<% toolDropdownId = 'quarto-navigation-tool-dropdown-' + toolCount %> |
|
<% toolCount = toolCount + 1 %> |
|
<div class="dropdown"> |
|
<a href="<%- tool.href %>" title="<%- tool.text %>" id="<%- toolDropdownId %>" class="quarto-navigation-tool dropdown-toggle px-1" data-bs-toggle="dropdown" aria-expanded="false" role="link" aria-label="<%- tool['aria-label'] || tool.text %>"<%= tool.target ? ` target="${tool.target}"` : "" %>><i class="bi bi-<%- tool.icon %>"></i></a> |
|
<ul class="dropdown-menu<%- dropDownAlignClz %>" aria-labelledby="<%- toolDropdownId %>"> |
|
<% tool.menu.forEach(item => { %> |
|
<li> |
|
<a class="dropdown-item <%- className %>-item" href="<%- item.href %>"<%= item.target ? ` target="${item.target}"` : "" %>> |
|
<% if (item.icon) { %> |
|
<i class="bi <%- item.icon %> pe-1"></i> |
|
<% } %> |
|
<%- item.text %> |
|
</a> |
|
</li> |
|
<% }) %> |
|
</ul> |
|
</div> |
|
<% } else { %> |
|
<a href="<%- tool.href %>" <%= tool.rel ? `rel="${tool.rel}"` : "" %> title="<%- tool.text %>" class="quarto-navigation-tool px-1" aria-label="<%- tool['aria-label'] || tool.text %>"<%= tool.target ? ` target="${tool.target}"` : "" %>><i class="bi bi-<%- tool.icon %>"></i></a> |
|
<% } %> |
|
<% }) %> |
Authors have little reason to set text: on a tool, since it never renders as visible text, only as the title tooltip. aria-label: is accepted on the item and does fix it, but nothing points authors there. Note the same lines already avoid emitting empty rel and target, so title and aria-label look like they were just missed in that pass.
We could omit both attributes when there is no value, and fall back to a name derived from the icon when neither text: nor aria-label: is given, so that the documented configuration produces a named link by default.
Related: #14380 and #14655 are the same axe rule on different elements, and #14373 collects a batch of axe-core findings on Quarto sites.
cc @cwickham as I don't know if you spotted this one, but 🤖 reported it to me while testing
The documented way to declare navbar and sidebar tools uses
icon:alone, with notext:. That renders links with an empty accessible name, which axe-core reports aslink-name.Taking the example straight from https://quarto.org/docs/websites/website-navigation.html#navbar-tools:
produces
The icon is drawn by a class on an empty
<i>, so there is no text node to fall back on. Chrome's accessibility tree announces both aslink "", and axe-core reportslink-name(serious,wcag2a/wcag244/wcag412) on three nodes for that config: the navbar tool, the navbar dropdown tool, and the sidebar tool. Sidebar tools go through the same template, sowebsite: sidebar: tools:is affected the same way, and the sidebar example in the docs has the same shape.aria-label=""is worse than no attribute at all, since an emptyaria-labelblanks the name rather than letting anything else supply one. Both attributes are interpolated fromtool.text, which is unset here:quarto-cli/src/resources/projects/website/templates/navtools.ejs
Lines 26 to 46 in 2117a09
Authors have little reason to set
text:on a tool, since it never renders as visible text, only as thetitletooltip.aria-label:is accepted on the item and does fix it, but nothing points authors there. Note the same lines already avoid emitting emptyrelandtarget, sotitleandaria-labellook like they were just missed in that pass.We could omit both attributes when there is no value, and fall back to a name derived from the icon when neither
text:noraria-label:is given, so that the documented configuration produces a named link by default.Related: #14380 and #14655 are the same axe rule on different elements, and #14373 collects a batch of axe-core findings on Quarto sites.
cc @cwickham as I don't know if you spotted this one, but 🤖 reported it to me while testing