From 29ece683323f0d8bd3ea7691698078f91316b226 Mon Sep 17 00:00:00 2001 From: Cell <1024@lruihao.cn> Date: Fri, 3 Apr 2026 18:00:17 +0800 Subject: [PATCH] feat(accessibility): enhance accessibility features across components and improve focus management (#725) --- assets/css/_core/_layout/_header.scss | 26 ++++++++++------- assets/css/_core/_mixin/_compatibility.scss | 19 ++++++++++++ assets/css/_core/_widgets/_fixed-button.scss | 6 ++-- assets/js/theme.js | 29 ++++++++++++++++++- hugo.toml | 4 +-- layouts/_partials/function/code-copy-btn.html | 2 +- .../_partials/function/code-expand-btn.html | 2 +- layouts/_partials/function/code-header.html | 14 ++++----- layouts/_partials/layout/header.html | 16 +++++----- layouts/_partials/layout/widgets.html | 6 ++-- .../_partials/plugin/diagram-copy-btn.html | 2 +- layouts/posts/single.html | 2 +- 12 files changed, 89 insertions(+), 39 deletions(-) diff --git a/assets/css/_core/_layout/_header.scss b/assets/css/_core/_layout/_header.scss index be468e2c..d531fe37 100644 --- a/assets/css/_core/_layout/_header.scss +++ b/assets/css/_core/_layout/_header.scss @@ -70,7 +70,8 @@ header { font-size: fixit-var(header-title-font-size); font-family: fixit-var(header-title-font-family); font-weight: bold; - margin-right: 0.5rem; + margin-inline-end: 0.25rem; + padding-inline: 0.25rem; min-width: 10%; overflow: hidden; white-space: nowrap; @@ -175,6 +176,7 @@ header { #header-desktop.open & { left: 1rem; } + @include focus-visible-ring; } .search-loading { @@ -210,6 +212,9 @@ header { } .theme-switch { + position: relative; + @include focus-visible-ring; + i { display: inline-block; transform: rotate(225deg) scale(1); @@ -246,12 +251,14 @@ header { } .header-wrapper { - // use css native function to calculate max value - padding: 0 2rem 0 MAX(10vh, fixit-var(header-height)); + padding: 0 2rem; height: 100%; + &[data-github-corner='left'] { + padding-inline-start: fixit-var(header-height); + } &[data-github-corner='right'] { - padding-right: fixit-var(header-height); + padding-inline-end: fixit-var(header-height); } .header-title { @@ -311,11 +318,7 @@ header { } @include media('md', 'down') { - padding-inline-end: 1rem; - } - - @include media('sm', 'down') { - padding-inline-start: 1rem; + padding-inline: 1rem; } } @@ -358,13 +361,16 @@ header { } .menu-toggle { + position: relative; + @include focus-visible-ring; + span { display: block; background-color: fixit-var(global-font-color); width: 20px; height: 2px; - @include border-radius(full); transition: all 0.3s ease-in-out; + @include border-radius(full); } span:not(:last-child) { diff --git a/assets/css/_core/_mixin/_compatibility.scss b/assets/css/_core/_mixin/_compatibility.scss index 28725aa5..ebf47431 100644 --- a/assets/css/_core/_mixin/_compatibility.scss +++ b/assets/css/_core/_mixin/_compatibility.scss @@ -61,3 +61,22 @@ } } } + +@mixin focus-visible-ring($size: 1.5em, $offset: 0.2rem) { + &:focus-visible { + outline: none; + + &::after { + content: ''; + position: absolute; + top: 50%; + left: 50%; + width: $size; + height: $size; + transform: translate(-50%, -50%); + outline: -webkit-focus-ring-color auto 1px; + outline-offset: $offset; + pointer-events: none; + } + } +} diff --git a/assets/css/_core/_widgets/_fixed-button.scss b/assets/css/_core/_widgets/_fixed-button.scss index 31769ae7..59a41edd 100644 --- a/assets/css/_core/_widgets/_fixed-button.scss +++ b/assets/css/_core/_widgets/_fixed-button.scss @@ -23,10 +23,8 @@ cursor: pointer; } - &:active, - &:focus, - &:hover { - outline: none; + &:focus-visible { + outline: -webkit-focus-ring-color auto 1px; } @include media('print') { diff --git a/assets/js/theme.js b/assets/js/theme.js index 0c588239..a35248b8 100644 --- a/assets/js/theme.js +++ b/assets/js/theme.js @@ -68,6 +68,19 @@ class FixIt { this.config.twemoji && twemoji.parse(target); } + initRoleButtons(target = document) { + forEach(target.querySelectorAll('[role="button"]'), ($el) => { + if ($el.dataset.init) return; + $el.dataset.init = 'true'; + $el.addEventListener('keydown', (e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + $el.click(); + } + }, false); + }); + } + initMenu() { this.initMenuDesktop(); this.initMenuMobile(); @@ -87,10 +100,12 @@ class FixIt { $mask.classList.toggle('blur'); $menuToggleMobile.classList.toggle('active'); $menuMobile.classList.toggle('active'); + $menuToggleMobile.setAttribute('aria-expanded', $menuToggleMobile.classList.contains('active') ? 'true' : 'false'); }, false); this._menuMobileOnClickMask = this._menuMobileOnClickMask || (() => { $menuToggleMobile.classList.remove('active'); $menuMobile.classList.remove('active'); + $menuToggleMobile.setAttribute('aria-expanded', 'false'); }); this.clickMaskEventSet.add(this._menuMobileOnClickMask); // add nested menu toggler @@ -166,6 +181,7 @@ class FixIt { $searchLoading.style.display = 'none'; $searchClear.style.display = 'none'; searchInstance && searchInstance.autocomplete.setVal(''); + document.getElementById(`search-toggle-${$header.id.replace('header-', '')}`)?.setAttribute('aria-expanded', 'false'); } initSearch() { @@ -198,6 +214,9 @@ class FixIt { const $searchClear = document.getElementById(`search-clear-${suffix}`); const $searchCancel = document.getElementById('search-cancel-mobile'); const $mask = document.getElementById('mask'); + const setSearchExpanded = (expanded) => { + $searchToggle?.setAttribute('aria-expanded', expanded ? 'true' : 'false'); + }; // goto the PostChat panel rather than search results if (searchConfig.type === 'post-chat' && window.postChatUser) { @@ -218,11 +237,13 @@ class FixIt { $searchInput.addEventListener('focus', () => { $mask.classList.add('blur'); $header.classList.add('open'); + setSearchExpanded(true); }, false); $searchCancel.addEventListener('click', () => { $mask.classList.remove('blur'); document.getElementById('menu-toggle-mobile').classList.remove('active'); document.getElementById('menu-mobile').classList.remove('active'); + document.getElementById('menu-toggle-mobile').setAttribute('aria-expanded', 'false'); this._resetSearchUI($header, $searchLoading, $searchClear, this._searchMobile); }, false); $searchClear.addEventListener('click', () => { @@ -239,6 +260,7 @@ class FixIt { $mask.classList.add('blur'); $header.classList.add('open'); $searchInput.focus(); + setSearchExpanded(true); }, false); $searchClear.addEventListener('click', () => { $searchClear.style.display = 'none'; @@ -262,7 +284,7 @@ class FixIt { dropdownMenuContainer: `#search-dropdown-${suffix}`, clearOnSelected: true, cssClasses: { noPrefix: true }, - debug: true + debug: false }, { name: 'search', @@ -908,11 +930,15 @@ class FixIt { if (!dialog || !openButton) return; openButton.addEventListener("click", () => { dialog.showModal(); + openButton.setAttribute('aria-expanded', 'true'); document.activeElement?.blur(); }); dialog.addEventListener("click", (e) => { dialog.close(); }); + dialog.addEventListener("close", () => { + openButton.setAttribute('aria-expanded', 'false'); + }); } /** @@ -1447,6 +1473,7 @@ class FixIt { this.initMathJax(); this.initJsonViewer(); this.initTabEvents(target); + this.initRoleButtons(target); FileTree.init(target); window.FixItMermaid?.init?.(); window.FixItAPlayer?.init?.(); diff --git a/hugo.toml b/hugo.toml index 5bd39c00..82b4012a 100644 --- a/hugo.toml +++ b/hugo.toml @@ -492,8 +492,8 @@ tileColor = "#da532c" # FixIt 0.2.12 | CHANGED Android browser theme color [params.app.themeColor] -light = "#f8f8f8" -dark = "#252627" +light = "#f6f8fa" +dark = "#151b23" # Search config [params.search] diff --git a/layouts/_partials/function/code-copy-btn.html b/layouts/_partials/function/code-copy-btn.html index ad98354c..a6166afb 100644 --- a/layouts/_partials/function/code-copy-btn.html +++ b/layouts/_partials/function/code-copy-btn.html @@ -4,7 +4,7 @@ {{- if $config.copyable -}} {{- $copyIcon := dict "Class" "fa-regular fa-clone fa-width-auto" | partial "plugin/icon.html" -}} - {{- $copyBtn := printf `