Skip to content

docs(a11y): add WCAG/POUR grounding, colour contrast, manual-test sections#3443

Merged
ten9876 merged 3 commits into
mainfrom
docs/a11y-additions
Jun 7, 2026
Merged

docs(a11y): add WCAG/POUR grounding, colour contrast, manual-test sections#3443
ten9876 merged 3 commits into
mainfrom
docs/a11y-additions

Conversation

@ten9876
Copy link
Copy Markdown
Collaborator

@ten9876 ten9876 commented Jun 6, 2026

Summary

Follow-up to #3289 closing five gaps surfaced by reviewing docs/a11y.md against the WCAG/POUR-grounded coverage at accessibilitychecker.org/blog/a11y/.

Our existing doc is materially deeper than that article on Qt-specific patterns (live value announcements, throttling, custom-painted widgets, lint mechanics) — but it was missing five concrete things every Qt-desktop a11y guide should have. All five land as pure additions; no existing section is modified.

What's added

  1. "What we're following" section — three-line POUR (Perceivable / Operable / Understandable / Robust) grounding right after the Background. Cites WCAG 2.1 quick-ref. Future contributors asking "why does this rule exist" now get "WCAG 2.1 says so" rather than "because the project says so."

  2. Form-field instructions guidance — added to the existing "Accessible description" bullet. For QSpinBox / QLineEdit / QComboBox, the accessible name answers "what is this?" and the description answers "what do I type?". Tooltips don't reach the screen reader; the description does.

  3. Keyboard-only test — added to the existing "Tab focus" bullet. Tab to the widget from a sibling, activate with Space/Return, no mouse. Links to the existing QLabel anti-pattern section as the structural escape.

  4. Colour contrast section — between "Live value updates" and "Custom-painted widgets." WCAG 4.5:1 normal text / 3:1 large text and non-text components. Cites fix(radio): make disabled Reboot Radio button legible instead of near-invisible (#3334) #3441 (Reboot Radio button) as the canonical disabled-state-blends-into-background case so future readers see why the section exists. WebAIM Contrast Checker + the OS-level "Increase Contrast" mode as the practical verification workflow.

  5. Manual verification section — between "Suppressing the lint" and "CI enforcement." Names the three platform screen readers (VoiceOver, NVDA / Narrator, Orca) with the actual keystrokes to launch them. Lint = static; this = real test. Explicit that "the lint catches what it can detect statically — this is the real test."

Doc shape

Before After
Lines 166 254
Sections 7 9

Section order now flows: motivation → grounding → patterns → escape valves → verification → mechanism.

What I didn't import from the article

  • Legal framing ("legally mandated… legal action or fines"). Out of voice. AetherSDR's a11y motivation is "a blind ham operator filed Phase 2 accessibility: live-region announcements + structural fixes for custom widgets #3288 and we want AetherSDR usable for him" — that's a stronger frame than fear of lawsuits.
  • POUR as the organizing structure of the whole doc. The article is structured around POUR; we'd lose clarity if we reshuffled our concrete Qt sections under POUR headings. Mention WCAG/POUR once near the top, keep our pragmatic structure.
  • Alt text on images / captions on video. We have ~zero of either in AetherSDR — no help text videos, the only "images" are panadapter renders (covered by our custom-painted-widgets section).

Test plan

  • docs/a11y.md parses as valid markdown (9 ## sections, no orphaned formatting).
  • Links checked: WCAG 2.1 quickref, WebAIM contrast checker + articles/contrast, NVDA, Apple Accessibility, Orca docs — all resolve.
  • No code changes — tools/check_a11y.py and the workflow are untouched, so no CI behaviour change.

Refs #3288 #3289

ten9876 added 2 commits June 6, 2026 17:42
…tions

Follow-up to #3289 closing five gaps surfaced by reviewing our doc against
the WCAG/POUR-grounded coverage at accessibilitychecker.org. Our existing
doc is materially deeper than that article on Qt-specific patterns (live
value announcements, throttling, custom-painted widgets, lint mechanics),
but it was missing five concrete things every Qt-desktop a11y guide
should have. All five land as additions; no existing section is changed.

1. "What we're following" — three-line POUR (Perceivable / Operable /
   Understandable / Robust) grounding right after the Background. Cites
   WCAG 2.1 quick-ref. Future contributors asking "why does this rule
   exist" now get "WCAG 2.1 says so" rather than "because the project
   says so."

2. Form-field instructions — added to the existing "Accessible
   description" bullet. For QSpinBox / QLineEdit / QComboBox, the
   accessible name answers "what is this?" and the description answers
   "what do I type?". Tooltips don't reach the screen reader; the
   description does — that's the right home for input semantics.

3. Keyboard-only test — added to the existing "Tab focus" bullet. Tab to
   the widget from a sibling, activate with Space/Return, no mouse. If
   you can't reach it the focus policy is wrong; if Space/Return doesn't
   fire it the widget needs a keyPressEvent (or it should have been a
   QPushButton — links to the QLabel anti-pattern section).

4. Colour contrast — new section between "Live value updates" and
   "Custom-painted widgets." WCAG 4.5:1 normal text / 3:1 large text and
   non-text components. Cites #3441 (Reboot Radio button) as the
   canonical disabled-state-blends-into-background case so future readers
   see why the section exists. WebAIM Contrast Checker + the
   OS-level "Increase Contrast" mode as the practical verification
   workflow.

5. Manual verification — new section between "Suppressing the lint" and
   "CI enforcement." Names the three platform screen readers (VoiceOver,
   NVDA/Narrator, Orca) with the actual keystrokes to launch them, and
   tells contributors to walk the widget the way a screen-reader user
   would. Lint = static; this = real test. Explicit that the lint
   catches what it can detect statically and nothing more.

Doc grows 166 → 254 lines (+88). Section order flows
motivation → grounding → patterns → escape valves → verification →
mechanism.

Principle XI.
Self-review of #3443 surfaced five issues; all five addressed here.

1. Colour contrast section now matches the theme-tokens-first policy
   established in #3441 / #3446 (instead of contradicting it).

   The earlier text read "pick concrete hex values that hit the WCAG
   ratio against the real surrounding background; reserve the dimmest
   theme tokens for genuinely decorative state where invisibility is the
   intent" — which advocated hex over tokens for disabled state and
   would have read as policy by future contributors. The actual policy
   from the #3441/#3446 thread: theme tokens are first choice; hex is a
   valid intermediate state when no semantic token exists, with a
   follow-up issue to add the proper token (mirroring how #3441 +
   #3446 played out). Adds the three-step rule of thumb (existing token
   for this widget × state? token for similar widget? hex + issue?) and
   cites #3441's merge commit and #3446's open status as the canonical
   example.

2. POUR Operable softened from overclaim to aspirational.

   Was: "Operable — keyboard focus and activation paths for every
   interactive widget; no mouse-only interactions." That's false today —
   CHAIN drag-drop, frequency drag, drag-to-pan, custom panadapter
   handlers are all mouse-only. Replaced with "every interactive widget
   reachable by keyboard, with a non-mouse activation path. (AetherSDR
   still has surfaces that are mouse-only today — [list] — which we're
   working toward; new code shouldn't add to the list.)"

3. Tooltip-vs-description claim corrected.

   Was: "right place to put input semantics, not in a separate tooltip
   the screen reader can't reach." Qt does expose tooltips to AT via
   QAccessible::Help — they reach screen readers as help text, fired on
   explicit request. The argument that descriptions are the better home
   for input semantics still stands, but now states the actual reason
   (descriptions are read automatically as part of widget identity;
   tooltips are help-text-on-request).

4. NVDA "say more" mode reference replaced with neutral language.

   "Say more" isn't a standard NVDA mode name (it may be confused with
   VoiceOver's "speak more"). Now says "in verbose announcement modes"
   without committing to a specific NVDA mode label.

5. WebAIM Contrast Checker workflow fixed.

   The previous text said "take a screenshot of the widget against its
   real background and drop it into the WebAIM Contrast Checker" — but
   that tool takes two hex codes, not an image. Reworded to mention the
   eyedropper step (Digital Color Meter on macOS, PowerToys Color
   Picker on Windows, gpick/gcolor3 on Linux) and added a pointer to
   Colour Contrast Analyser (TPGi) for a one-step
   screenshot-to-ratio workflow with integrated eyedropper.

Doc grows 254 → 286 lines net (+74 modifications inside the existing
+89 from the original commit). Section structure unchanged.

Principle XI.
…tters

The per-method opt-out example showed the // a11y-check: skip comment on
the line above the method:

    // a11y-check: skip
    void FooWidget::setInternalCache(float v) { ... }

But tools/check_a11y.py:200-203 only inspects (a) the method definition
line itself and (b) the body between { and }. A comment on the line above
the method is invisible to it, so a contributor copying the example
as-written would still get a value-method-missing-a11y-update warning.

The prose immediately above the example already says correctly "on the
method line or anywhere in its body" — only the code example was wrong.
Replaced with two valid placements (definition line + body line) and
explicit "checker only reads those two locations" so future readers see
why the line above doesn't work.

Principle XI.
@ten9876 ten9876 merged commit f053c51 into main Jun 7, 2026
4 checks passed
@ten9876 ten9876 deleted the docs/a11y-additions branch June 7, 2026 04:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant