Accessibility in Gextto¶
How the interface is kept usable with a keyboard, a screen reader, a magnifier and a phone: what is automated, what is checked by hand, and what a change has to pass before it is finished.
Target: WCAG 2.1 AA, compatible with the 2.2 practices where they do not change the interface. This is not a certification. The automated baseline passes; a formal declaration also needs the manual audit described below.
Where it applies¶
| Interface | What it is |
|---|---|
Gextto web UI (uiweb/v2) |
Every SSR + HTMX page: dashboard, downloads, series, movies, comics, archive, settings, maintenance, health, logs, integrations |
gx-torrent page (cmd/gx-torrent) |
Standalone client: torrent list, context and column menus, detail panel, RSS and Wanted editors |
TUI (gexttod tui) |
Keyboard only by nature; the layout compacts below 60 columns for an SSH session on a phone |
| Manuals | Markdown served in the app: headings, lists and links are read by screen readers |
What is in place¶
- Semantic HTML: landmarks, skip link,
aria-current, headings in order, lists for lists. - Every control has an accessible name; icon buttons carry text or
aria-label; fields have alabel, not just a placeholder. - Dialogs: focus enters the dialog,
Tabis trapped,Esccloses, focus returns to the control that opened it. - Keyboard: every action is reachable; sortable headers expose
aria-sort; column and panel resizers exposearia-valuenow; menus are navigable; nothing works only with a mouse. - Live regions announce async states, errors, progress and notifications; polling does not move the focus.
- Tables have a caption and scope; the torrent table is a
role="grid"witharia-multiselectable; progress bars carry a value. - Contrast: colour tokens for light and dark themes, checked minimums; the 20+ themes and the custom ones use the same tokens.
- Reflow at 320 px without horizontal scroll; reduced motion respected; zoom 200 % and 400 % supported.
- Six languages, including the strings generated by the client JS. Labels, descriptions
and tooltips are translated and checked by
make test.
Automated checks¶
Playwright with axe-core, uiweb/end2end/tests/accessibility.spec.ts:
- axe with the
wcag2aandwcag2aatags on/,?view=downloads,?view=settings,?view=maintenance,?view=integrations,?view=health,?view=logs: no violations; - accessible names on the search and language controls;
- the settings search understands common synonyms;
- dialogs keep the focus and return it on close;
- polling does not lose the focus on the torrent control;
- the release search works from the keyboard;
- no horizontal scroll at 320 px;
- scrollable log areas are keyboard reachable and have a region role.
Run the suite:
cd uiweb/end2end
npm ci
npm run test:a11y # 14 tests
The CI job ui-e2e runs the whole Playwright suite on every push, so a regression on
these tests blocks the build.
The static guards in make test are not a substitute for axe, but they prevent controls
that cannot even be read: every setting is indexed, writable, translated in six
languages and has a tooltip, and every button of the UI points at a registered route
(TestUIIsTranslatable, TestSettingTooltipsAreTranslated,
TestSettingsIndexIsWritable, TestUIActionPathsAreRegisteredRoutes).
Manual verification¶
Before a release, and after any UI change that touches focus, layout, contrast or async states:
- keyboard only:
Tab/Shift+Taborder,Enter/Space, arrows,Esc; every dialog, menu, table and form; - a desktop screen reader (NVDA + Firefox or VoiceOver + Safari) and TalkBack on a phone;
- zoom 200 % and 400 %: nothing clipped, dialogs usable, no horizontal scroll;
- forms with the screen reader: error summary, field association, invalid state, recovery;
- async states not colour-only: loading, success, warning, failure, empty, retry;
- both themes, a mobile viewport, reduced motion;
- external content injected by the page (covers, release names) does not break the reading order.
Acceptance criteria¶
- Every control has a reliable name and role.
- Every mouse action has a keyboard equivalent.
- Focus is contained in dialogs and restored when they close.
- Async announcements are informative and not excessive.
- Contrast passes in both themes.
- Tables, tabs, progress bars and errors have usable semantics.
- No regression on desktop or mobile.
Adding or changing a control¶
- Use the existing component (
label+.input,dialog,ul.items,.chip,.badge,.fsec) and the colour tokens; never a fixed colour. - Give the control a label (or
aria-label/aria-labelledby) and, for an icon, a text alternative. - Check it from the keyboard: what receives focus, what
Tabreaches next, whatEnter/Space/Escdo, where the focus returns. - If a state changes asynchronously, announce it in a live region.
- Add the string and its tooltip to the six catalogues;
make testenforces it. - Run
npm run test:a11y. For a new page, add it to the axe list inaccessibility.spec.ts. - For anything that changes interaction, do the manual check on a phone and with a screen reader.
A UI change without this check is not finished.
Audit and known limits¶
- The gx-torrent pages were audited with axe-core (light and dark theme, sample torrents,
feeds, rules and wanted titles) with 0 violations. The first audit found and led to
fixing: resizers without a value, zero-count filters below the contrast minimum,
aria-selectedrows outside a grid,aria-expandedon a row, preview lists that could not take the focus, and the progress percentage drawn over the bar. - The automated suite is a safety net, not a certification. The manual screen-reader, zoom and assistive-technology pass is still required for a WCAG/EN 301 549 statement.
- The TUI relies on plain text, a stable order and the terminal's own screen-reader support; it has no graphical semantics of its own.
- The accessibility results of each round, with the machine and the date, are in
measurements.md.