From c90c98db275e1ccb5e535e6434512aa9be0c33e8 Mon Sep 17 00:00:00 2001 From: SandObserver <260779319+SandObserver@users.noreply.github.com> Date: Sun, 23 Aug 2026 10:56:59 -0400 Subject: [PATCH] Fix accessibility defects across settings, widgets and the dashboard Name the settings toggles from the row label beside them, rather than from an aria-label on the wrapping label element, where ARIA discards it and the checkbox resolved an empty name. Link each inline-edit field to its row label, and resolve an inline-edit placeholder on use so a translated one is read after the catalog loads. Raise the type pills and the unselected filter chip above 4.5:1 on both the pane and a card in both themes, and the digital clock's date line above it in both of its modes. Teach the contrast test to resolve color-mix() and to composite a translucent fill over a backdrop, and pin those pairs. Wrap the settings content in a main landmark and make the dashboard pager a navigation landmark. Number repeated widget frame titles. Mark every dashboard page but the current one inert, on the home pager and in the folder overlay: an off-screen page stayed in the tab order, so Tab left the visible page for tiles nobody could see. Widen the pagination dot pitch to 24px, which is an interior dot's effective width. Remove the live region from System Summary, Weather, DNS and Now Playing, which announced their polled figures on a timer rather than after a user action. Build the books shelf as a real list and inset its hover panel from the tile edge. Translate the accessible names in the GitHub, Disk Health, Now Playing, Connections and Dashboard Switch widgets, the reorder buttons, and the pull request filter names. Add an accessibility statement. --- ACCESSIBILITY.md | 40 +++++++ CHANGELOG.md | 33 ++++++ README.md | 4 + ui/admin/index.html | 20 ++-- ui/css/admin.css | 26 ++-- ui/css/dashboard.css | 4 +- ui/i18n/de.json | 4 +- ui/i18n/en.json | 4 +- ui/i18n/es.json | 4 +- ui/i18n/fa.json | 4 +- ui/i18n/fr.json | 4 +- ui/i18n/zh-Hans.json | 4 +- ui/index.html | 6 +- ui/js/admin-app-form.js | 4 +- ui/js/admin-auth.js | 2 +- ui/js/admin-color-control.js | 2 +- ui/js/admin-settings.js | 2 +- ui/js/admin-shared.js | 19 ++- ui/js/admin-widget-form.js | 4 +- ui/js/admin.js | 16 +-- ui/js/dashboard.js | 25 +++- ui/js/ui.js | 12 +- ui/js/widget-config-form.js | 4 +- ui/js/widget-types.js | 10 ++ ui/test/books-list-semantics.test.mjs | 71 +++++++++++ ui/test/contrast.test.mjs | 98 +++++++++++++++- ui/test/control-names.test.mjs | 111 ++++++++++++++++++ ui/test/landmarks.test.mjs | 37 ++++++ ui/test/offscreen-pages.test.mjs | 59 ++++++++++ ui/test/pagination.test.mjs | 9 +- ui/test/widget-i18n.test.mjs | 41 +++++++ ui/test/widget-live-regions.test.mjs | 91 ++++++++++++++ ui/widgets/books/index.html | 38 +++--- ui/widgets/clock/digital.html | 4 +- ui/widgets/connections/connections-map.html | 2 +- ui/widgets/connections/i18n/de.json | 3 +- ui/widgets/connections/i18n/en.json | 3 +- ui/widgets/connections/i18n/es.json | 3 +- ui/widgets/connections/i18n/fa.json | 3 +- ui/widgets/connections/i18n/fr.json | 3 +- ui/widgets/connections/i18n/zh-Hans.json | 3 +- ui/widgets/dashboard-switch/i18n/de.json | 3 +- ui/widgets/dashboard-switch/i18n/en.json | 3 +- ui/widgets/dashboard-switch/i18n/es.json | 3 +- ui/widgets/dashboard-switch/i18n/fa.json | 3 +- ui/widgets/dashboard-switch/i18n/fr.json | 3 +- ui/widgets/dashboard-switch/i18n/zh-Hans.json | 3 +- ui/widgets/dashboard-switch/index.html | 2 +- ui/widgets/disk-health/i18n/de.json | 4 +- ui/widgets/disk-health/i18n/en.json | 4 +- ui/widgets/disk-health/i18n/es.json | 4 +- ui/widgets/disk-health/i18n/fa.json | 4 +- ui/widgets/disk-health/i18n/fr.json | 4 +- ui/widgets/disk-health/i18n/zh-Hans.json | 4 +- ui/widgets/disk-health/index.html | 4 +- ui/widgets/dns/index.html | 2 +- ui/widgets/github/contributions.html | 3 +- ui/widgets/github/data.js | 11 +- ui/widgets/github/i18n/de.json | 4 +- ui/widgets/github/i18n/en.json | 4 +- ui/widgets/github/i18n/es.json | 4 +- ui/widgets/github/i18n/fa.json | 4 +- ui/widgets/github/i18n/fr.json | 4 +- ui/widgets/github/i18n/zh-Hans.json | 4 +- ui/widgets/github/pullrequests.html | 8 +- ui/widgets/nowplaying/i18n/de.json | 3 +- ui/widgets/nowplaying/i18n/en.json | 3 +- ui/widgets/nowplaying/i18n/es.json | 3 +- ui/widgets/nowplaying/i18n/fa.json | 3 +- ui/widgets/nowplaying/i18n/fr.json | 3 +- ui/widgets/nowplaying/i18n/zh-Hans.json | 3 +- ui/widgets/nowplaying/index.html | 4 +- ui/widgets/system-summary/index.html | 2 +- ui/widgets/weather/index.html | 2 +- 74 files changed, 831 insertions(+), 127 deletions(-) create mode 100644 ACCESSIBILITY.md create mode 100644 ui/test/books-list-semantics.test.mjs create mode 100644 ui/test/control-names.test.mjs create mode 100644 ui/test/landmarks.test.mjs create mode 100644 ui/test/offscreen-pages.test.mjs create mode 100644 ui/test/widget-live-regions.test.mjs diff --git a/ACCESSIBILITY.md b/ACCESSIBILITY.md new file mode 100644 index 00000000..fdff2cd3 --- /dev/null +++ b/ACCESSIBILITY.md @@ -0,0 +1,40 @@ +# Accessibility + +Stackyard targets [WCAG 2.1](https://www.w3.org/TR/WCAG21/) and [WCAG 2.2](https://www.w3.org/TR/WCAG22/) at level AA. + +## Conformance status + +Stackyard is **partially conformant** with WCAG 2.2 level AA. Partially conformant means most of the standard is met, and the parts that are not are listed below. + +This is a self-assessment. No external audit has been carried out. + +## What is supported + +- Every control has a name, a role, and a visible focus indicator. +- The interface is operable by keyboard alone. Reordering, paging, search, and dialogs each have a keyboard route. +- Dialogs trap focus, close on Escape, and return focus to the control that opened them. +- Text in the dashboard and in Settings meets the 4.5:1 contrast minimum in both the light and the dark theme. The ratios are computed from the stylesheets by a test, so a colour change that drops a pair below the minimum fails the build. +- The interface honours `prefers-reduced-motion` and `prefers-contrast`. +- Content reflows to a 320 CSS pixel viewport with no horizontal scrolling, and survives text resizing to 200%. +- The interface is translated into six languages. Accessible names are translated with it, apart from those listed below. +- Page changes are announced. Polled figures are not announced, so a screen reader is not interrupted by data that changes on a timer. + +## Known limitations + +- **Widgets need an extra step to read.** Each widget is an embedded document. A screen reader does not enter one during linear navigation, so its contents are read only after the reader is asked to enter it. Widgets containing a button or a link are also reachable with Tab. +- **A phone shortcut that responds only to touch.** On a phone, a widget that has a link set opens it when tapped away from the widget's own controls. There is no keyboard equivalent for that shortcut. Widgets that carry their own link also expose it as a control, and that one is reachable by keyboard. The shortcut does not exist on a desktop. +- **Widget colours are not covered by the contrast test.** Each widget is a separate document with its own stylesheet. Those colours are checked by hand rather than by the build, and a widget rendered on a transparent background cannot be measured automatically. + +## How this was assessed + +- Automated testing with [axe-core](https://github.com/dequelabs/axe-core) in Chromium and WebKit, against the released container. +- Manual browser testing for reflow, text resize, text spacing, target size, and keyboard operation. +- Manual screen reader testing with VoiceOver, on macOS with Safari and on iOS. + +Automated testing has covered every widget. Screen reader testing has covered the dashboard, Settings, and the widgets on a running dashboard, but not every widget in every configuration. The Windows screen readers NVDA and JAWS have not been used. + +## Reporting a problem + +Open an issue at [github.com/SandObserver/stackyard/issues](https://github.com/SandObserver/stackyard/issues). Include the page, the assistive technology and browser, and what you expected to happen. + +Last reviewed 2026-08-23, against Stackyard 1.8.0 and later. diff --git a/CHANGELOG.md b/CHANGELOG.md index 228a3302..ab4fa15e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,39 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- An accessibility statement, in `ACCESSIBILITY.md`. + +### Changed + +- Settings content is now a main landmark, and the dashboard pager is a + navigation landmark. + +- Repeated widgets of one type now get distinct frame names. + +### Fixed + +- Toggles and inline-edit fields in Settings now carry an accessible name. + +- Type pills, filter chips and the digital clock's date line now meet the WCAG + contrast minimum in both themes. + +- The password field's placeholder now renders its translation. + +- Accessible names in the GitHub, Disk Health, Now Playing, Connections and + Dashboard Switch widgets are now translated. + +- Tab no longer moves to tiles on a dashboard page that is off screen. + +- Pagination dots now meet the minimum target size. + +- Widgets no longer read their figures aloud on every poll. System Summary, + Weather, DNS and Now Playing announced themselves on a timer. + +- The Move up and Move down buttons in Settings, and the pull request filter + names in the GitHub widget, are now translated. + ## [1.8.0] - 2026-08-23 ### Added diff --git a/README.md b/README.md index 042a7ce8..9d5e7683 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,10 @@ Full instructions, including building from source, are at [Installation](https:/ Stackyard never returns stored secrets to the browser, guards the URLs you test in the admin UI against SSRF and pins the resolved IP, and bounds every upstream call so one slow service cannot hang the dashboard. Some features trade safety for convenience and are opt-in with warnings. Read [docs/security.md](docs/security.md) before exposing Stackyard beyond your LAN. +## Accessibility + +Stackyard targets WCAG 2.2 level AA and is partially conformant. Every control is named and reachable by keyboard, colours meet the contrast minimum in both themes, and the interface honours the reduced-motion and increased-contrast settings. Known limitations and how this was assessed are in [ACCESSIBILITY.md](ACCESSIBILITY.md). + ## Contributing Contributions are welcome, within the constraints that keep Stackyard small and auditable (one container, no backend dependencies, vanilla frontend). See [CONTRIBUTING.md](CONTRIBUTING.md) and [docs/frontend.md](docs/frontend.md). Participation is covered by the [Code of Conduct](CODE_OF_CONDUCT.md). diff --git a/ui/admin/index.html b/ui/admin/index.html index 4ede3b57..1975848f 100644 --- a/ui/admin/index.html +++ b/ui/admin/index.html @@ -11,7 +11,7 @@ - +
@@ -48,7 +48,7 @@ -Errors shows warnings and errors only. Security events are always logged.
@@ -100,7 +100,7 @@Sets the appearance of the Settings pages. It is stored in this browser, applies to this device only, and takes effect at once.
Stops the screen dimming or locking while the dashboard is open. Browsers only allow this over HTTPS, so it has no effect on a plain HTTP address.
@@ -260,7 +260,7 @@