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 81babc982..231dd8e59 100644 --- a/ui/src/css/doc.css +++ b/ui/src/css/doc.css @@ -132,12 +132,20 @@ content: "\00a7"; } +/* A focusable control that only appears on hover is invisible to anyone + * tabbing to it, so every reveal below pairs hover with focus. */ .doc h1:hover .anchor, .doc h2:hover .anchor, .doc h3:hover .anchor, .doc h4:hover .anchor, .doc h5:hover .anchor, -.doc h6:hover .anchor { +.doc h6:hover .anchor, +.doc h1 .anchor:focus-visible, +.doc h2 .anchor:focus-visible, +.doc h3 .anchor:focus-visible, +.doc h4 .anchor:focus-visible, +.doc h5 .anchor:focus-visible, +.doc h6 .anchor:focus-visible { visibility: visible; } @@ -841,7 +849,8 @@ z-index: 1; } -.doc .listingblock:hover .source-toolbox { +.doc .listingblock:hover .source-toolbox, +.doc .listingblock:focus-within .source-toolbox { visibility: visible; } @@ -863,7 +872,6 @@ background: none; border: none; color: inherit; - outline: none; padding: 0; font-size: inherit; line-height: inherit; @@ -871,6 +879,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 33ee316e3..55dbf8260 100644 --- a/ui/src/css/header.css +++ b/ui/src/css/header.css @@ -189,8 +189,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; } @@ -201,7 +206,6 @@ body { .navbar-burger { background: none; border: none; - outline: none; line-height: 1; position: relative; padding: 0; @@ -213,6 +217,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 1c039cb33..c0d4ac7d9 100644 --- a/ui/src/css/nav.css +++ b/ui/src/css/nav.css @@ -117,7 +117,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); @@ -125,11 +124,17 @@ 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); } -.nav-panel-menu.is-active:hover .nav-menu-toggle { +.nav-panel-menu.is-active:hover .nav-menu-toggle, +.nav-panel-menu.is-active:focus-within .nav-menu-toggle { visibility: visible; } @@ -173,7 +178,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; @@ -183,6 +187,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..edc28f0d7 --- /dev/null +++ b/ui/src/js/09-heading-anchors.js @@ -0,0 +1,16 @@ +;(function () { + 'use strict' + + // Asciidoctor emits section anchors as an empty inside the + // heading, so they are links with no accessible name -- one per section, and + // axe flags every one. 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 b25259f7f..169d3b1ba 100644 --- a/ui/src/partials/footer-content.hbs +++ b/ui/src/partials/footer-content.hbs @@ -4,7 +4,7 @@