From 7388ad29bee33c179693d2ccab28efdb0c27d901 Mon Sep 17 00:00:00 2001 From: Lars Francke Date: Thu, 3 Sep 2026 12:37:05 +0200 Subject: [PATCH 1/2] fix: Make keyboard focus visible and name the unnamed controls Seven controls in the chrome carried `outline: none` with no replacement, so keyboard focus was invisible on the burger menu, the nav toggles, the TOC links, the copy button and every `summary`. asciidoctor-tabs does the same to its tab list items; that one is overridden here because its stylesheet is imported first. The rings use `currentcolor` rather than a new variable, so the design token work has nothing extra to reconcile and contrast stays out of scope. The search opener was a `div`, so it was neither focusable nor named and `Ctrl+K` was the only way in. Making it a `button` requires the popover and its results list to become siblings, because a button may not contain interactive content, which also stops a click inside the popover bubbling back to the opener and reopening it. The two logos, the four social links, the toolbar home link and Asciidoctor's per-heading section anchors were all reachable with no accessible name. The anchors are empty by construction, with the glyph coming from CSS, so they are named from their heading at runtime. --- ui/build.mjs | 2 -- ui/src/css/base.css | 6 +++++- ui/src/css/doc.css | 6 +++++- ui/src/css/header.css | 11 ++++++++++- ui/src/css/nav.css | 12 ++++++++++-- ui/src/css/tabs.css | 7 +++++++ ui/src/css/toc.css | 6 +++++- ui/src/css/toolbar.css | 6 +++++- ui/src/js/08-search.js | 7 +++++++ ui/src/js/09-heading-anchors.js | 15 +++++++++++++++ ui/src/partials/footer-content.hbs | 12 +++--------- ui/src/partials/header.hbs | 14 +++++++------- ui/src/partials/toolbar.hbs | 2 +- 13 files changed, 80 insertions(+), 26 deletions(-) create mode 100644 ui/src/js/09-heading-anchors.js diff --git a/ui/build.mjs b/ui/build.mjs index c32762fee..cf9dcb2cf 100644 --- a/ui/build.mjs +++ b/ui/build.mjs @@ -158,10 +158,8 @@ const faVersion = JSON.parse(readFileSync(resolve(faDir, 'package.json'), 'utf8' const icons = { 'external-link': 'solid/arrow-up-right-from-square', search: 'solid/magnifying-glass', - xing: 'brands/xing', linkedin: 'brands/linkedin', github: 'brands/github', - twitter: 'brands/twitter', link: 'solid/link' }; const symbols = Object.entries(icons).map(([name, faIcon]) => { diff --git a/ui/src/css/base.css b/ui/src/css/base.css index 70b28f1ad..2418b1fb8 100644 --- a/ui/src/css/base.css +++ b/ui/src/css/base.css @@ -88,7 +88,11 @@ button::-moz-focus-inner { summary { cursor: pointer; -webkit-tap-highlight-color: transparent; - outline: none; +} + +summary:focus-visible { + outline: 2px solid currentcolor; + outline-offset: 2px; } table { diff --git a/ui/src/css/doc.css b/ui/src/css/doc.css index cc6f5cf63..6ae6e52f8 100644 --- a/ui/src/css/doc.css +++ b/ui/src/css/doc.css @@ -875,7 +875,6 @@ background: none; border: none; color: inherit; - outline: none; padding: 0; font-size: inherit; line-height: inherit; @@ -883,6 +882,11 @@ height: 1em; } +.doc .source-toolbox .copy-button:focus-visible { + outline: 2px solid currentcolor; + outline-offset: 2px; +} + .doc .source-toolbox .copy-icon { flex: none; width: inherit; diff --git a/ui/src/css/header.css b/ui/src/css/header.css index 473f675fd..c877e95be 100644 --- a/ui/src/css/header.css +++ b/ui/src/css/header.css @@ -193,8 +193,13 @@ body { color: var(--navbar-menu-font-color-hover); } +/* font: inherit is required, not tidiness: .svg-icon sizes the magnifier in em, + * so the UA button font would shrink it from 16px to 13px. */ #search-button { + background: none; + border: none; cursor: pointer; + font: inherit; padding-right: 2rem; } @@ -205,7 +210,6 @@ body { .navbar-burger { background: none; border: none; - outline: none; line-height: 1; position: relative; padding: 0; @@ -217,6 +221,11 @@ body { min-width: 0; } +.navbar-burger:focus-visible { + outline: 2px solid currentcolor; + outline-offset: 2px; +} + .navbar-burger span { background-color: var(--navbar-font-color); height: 1.5px; diff --git a/ui/src/css/nav.css b/ui/src/css/nav.css index d33e939b1..d151534fc 100644 --- a/ui/src/css/nav.css +++ b/ui/src/css/nav.css @@ -121,7 +121,6 @@ height: 1em; margin-right: -0.5rem; opacity: 0.75; - outline: none; padding: 0; position: sticky; top: calc((var(--nav-line-height) - 1 + 0.5) * 1rem); @@ -129,6 +128,11 @@ width: 1em; } +.nav-menu-toggle:focus-visible { + outline: 2px solid currentcolor; + outline-offset: 2px; +} + .nav-menu-toggle.is-active { background-image: url(../img/octicons-16.svg#view-fold); } @@ -177,7 +181,6 @@ .nav-item-toggle { background: transparent url(../img/caret.svg) no-repeat center / 50%; border: none; - outline: none; line-height: inherit; padding: 0; position: absolute; @@ -187,6 +190,11 @@ margin-left: calc(var(--nav-line-height) * -1em); } +.nav-item-toggle:focus-visible { + outline: 2px solid currentcolor; + outline-offset: 2px; +} + .nav-item.is-active > .nav-item-toggle { transform: rotate(90deg); } diff --git a/ui/src/css/tabs.css b/ui/src/css/tabs.css index 215f17c18..94b1c63ba 100644 --- a/ui/src/css/tabs.css +++ b/ui/src/css/tabs.css @@ -2,3 +2,10 @@ .tabs { margin-top: 1rem; } + +/* asciidoctor-tabs sets `outline: none` on its tab list items, so keyboard users + * cannot see which tab they are on. Imported before this file, so this wins. */ +.tablist > ul li:focus-visible { + outline: 2px solid currentcolor; + outline-offset: 2px; +} diff --git a/ui/src/css/toc.css b/ui/src/css/toc.css index 01a77c2aa..94f93d3fd 100644 --- a/ui/src/css/toc.css +++ b/ui/src/css/toc.css @@ -76,7 +76,11 @@ .sidebar.toc .toc-menu a { display: block; - outline: none; +} + +.sidebar.toc .toc-menu a:focus-visible { + outline: 2px solid currentcolor; + outline-offset: 2px; } .toc .toc-menu a:hover { diff --git a/ui/src/css/toolbar.css b/ui/src/css/toolbar.css index f545901d4..d3470e3f2 100644 --- a/ui/src/css/toolbar.css +++ b/ui/src/css/toolbar.css @@ -30,7 +30,6 @@ background: url(../img/menu.svg) no-repeat 50% 47.5%; background-size: 49%; border: none; - outline: none; line-height: inherit; padding: 0; height: var(--toolbar-height); @@ -38,6 +37,11 @@ margin-right: -0.25rem; } +.nav-toggle:focus-visible { + outline: 2px solid currentcolor; + outline-offset: 2px; +} + @media screen and (min-width: 1024px) { .nav-toggle { display: none; diff --git a/ui/src/js/08-search.js b/ui/src/js/08-search.js index 1e79b397b..d9feb140a 100644 --- a/ui/src/js/08-search.js +++ b/ui/src/js/08-search.js @@ -4,6 +4,7 @@ function openSearchPopover () { document.getElementById('search-background').style.display = 'block' document.getElementById('search').style.display = 'block' + setSearchExpanded(true) // the search assets load on first use (see footer-scripts.hbs) window.loadSearch().then(function () { @@ -17,6 +18,12 @@ function closeSearchPopover () { document.getElementById('search-background').style.display = 'none' document.getElementById('search').style.display = 'none' + setSearchExpanded(false) + } + + function setSearchExpanded (expanded) { + var button = document.getElementById('search-button') + if (button) button.setAttribute('aria-expanded', String(expanded)) } function focusSearchInput () { diff --git a/ui/src/js/09-heading-anchors.js b/ui/src/js/09-heading-anchors.js new file mode 100644 index 000000000..48d057a30 --- /dev/null +++ b/ui/src/js/09-heading-anchors.js @@ -0,0 +1,15 @@ +;(function () { + 'use strict' + + // Asciidoctor emits section anchors as an empty inside the + // heading, so they are links with no accessible name. + // The glyph comes from CSS, so there is no text to find: name them after the heading they link to. + var anchors = document.querySelectorAll('.doc a.anchor') + Array.prototype.forEach.call(anchors, function (anchor) { + if (anchor.getAttribute('aria-label')) return + var heading = anchor.parentNode + if (!heading) return + var text = heading.textContent.trim() + if (text) anchor.setAttribute('aria-label', 'Link to ' + text) + }) +})() diff --git a/ui/src/partials/footer-content.hbs b/ui/src/partials/footer-content.hbs index 12591322d..864539bfa 100644 --- a/ui/src/partials/footer-content.hbs +++ b/ui/src/partials/footer-content.hbs @@ -4,7 +4,7 @@