Docs: Add WCAG 2.2 AAA compliance design document

Comprehensive design covering color system overhaul, component fixes,
accessibility properties, keyboard navigation, and reduced motion support.
This commit is contained in:
Your Name
2026-02-18 20:53:25 +02:00
parent 798a35e6d9
commit 937061f710

View File

@@ -0,0 +1,204 @@
# WCAG 2.2 AAA Compliance Design
**Date:** 2026-02-18
**Approach:** Style-System-First (cascade from SettingsStyle.qml, then per-component fixes)
**Goal:** Full WCAG 2.2 AAA compliance while preserving the current visual identity
---
## Section 1: Color System Overhaul (SettingsStyle.qml)
Update the central design token singleton to meet AAA contrast ratios.
| Token | Current | New | Rationale |
|---|---|---|---|
| `accentPurple` | `#7000FF` (1.6:1) | `#B794F6` (7.2:1) | AAA normal text on #121212 |
| `textSecondary` | `#999999` (6.3:1) | `#ABABAB` (8.1:1) | AAA normal text |
| `borderSubtle` | `rgba(1,1,1,0.08)` | `rgba(1,1,1,0.22)` | 3:1 non-text contrast |
New tokens to add:
- `textDisabled: "#808080"` (4.0:1 minimum for disabled states)
- `focusRingWidth: 2` (consistent focus indicator width)
- `minTargetSize: 24` (WCAG 2.5.8 minimum target)
---
## Section 2: Component Default Fixes
Bulk fixes that cascade from component-level defaults.
**ModernSlider.qml:**
- Handle: 18x18 -> 24x24 (meets 24px target size)
- Track: 4px -> 6px height
- Add 2px focus ring on `activeFocus`
- Add `Accessible.role: Accessible.Slider`
**ModernSwitch.qml:**
- Add "I" (on) / "O" (off) pip marks inside the thumb for non-color state indication
- Switch border to `borderSubtle` token
- Add `Accessible.role: Accessible.CheckBox` with state in name
**ModernTextField.qml:**
- Placeholder: `#606060` -> `#808080` (3.7:1 minimum)
- Focus border: 1px -> 2px
- Add `Accessible.role: Accessible.EditableText`
**ModernComboBox.qml:**
- Border -> `borderSubtle` token
- Focus border: 1px -> 2px
- Arrow icon: `#888888` -> `#ABABAB`
- Add `Accessible.role: Accessible.ComboBox`
**ModernKeySequenceRecorder.qml:**
- Border -> `borderSubtle` token
- Focus border: 1px -> 2px
- "None" text: `#808080` -> `#ABABAB`
- Add `activeFocusOnTab: true`
**GlowButton.qml:**
- Default text: `#9499b0` -> `#ABABAB`
- Border -> `borderSubtle` token
---
## Section 3: Individual Component Attention
Per-file hardcoded color fixes that can't be solved by token changes alone.
**Settings.qml:**
- StatBox accent colors: `#bd93f9` -> `#CAA9FF`, `#ff79c6` -> `#FF8FD0`, `#ff5555` -> `#FF8A8A`
- Close button hover: `#ff4b4b` -> `#FF8A8A`
- Model info text: 10px -> 11px, remove `opacity: 0.6`
- INSTALLED tag: verify contrast of green-on-dark
**Loader.qml:**
- Subtitle: `#80ffffff` -> `#ABABAB`
- Status text: remove `opacity: 0.8`, use full `textSecondary`
- Border: `#40ffffff` -> `rgba(1,1,1,0.22)`
**Overlay.qml:**
- Border: `#40ffffff` -> `rgba(1,1,1,0.22)` (non-text contrast)
---
## Section 4: Accessibility Properties
Add `Accessible.name` and `Accessible.role` to every interactive and informational element.
**Window-level:**
- Overlay.qml: `Accessible.name: "WhisperVoice Overlay"`, `title: "WhisperVoice"`
- Settings.qml: `Accessible.name: "WhisperVoice Settings"`, `title: "WhisperVoice Settings"`
- Loader.qml: `Accessible.name: "WhisperVoice Loading"`, `title: "WhisperVoice"`
**Overlay.qml elements:**
- Mic button: `Accessible.name: ui.isRecording ? "Stop recording" : "Start recording"`, `Accessible.role: Accessible.Button`
- Status text: `Accessible.name: statusText.text`, `Accessible.role: Accessible.AlertMessage`
- Transcription text: `Accessible.name: transcriptionText.text`, `Accessible.role: Accessible.StaticText`
**Settings.qml elements:**
- Nav items: `Accessible.name: modelData`, `Accessible.role: Accessible.Tab`
- Close button: `Accessible.name: "Close settings"`, `Accessible.role: Accessible.Button`
- Each settings section: delegate to ModernSettingsSection
- Tab content areas: `Accessible.role: Accessible.PageTab`
**Shared components:**
- ModernSettingsSection: `Accessible.name: root.title + " settings group"`, `Accessible.role: Accessible.Grouping`
- ModernSettingsItem: `Accessible.name: root.label`, `Accessible.role: Accessible.Row`
---
## Section 5: Keyboard Navigation
Make every interactive element operable via keyboard alone.
**Overlay.qml mic button:**
- Add `activeFocusOnTab: true`
- Add `Keys.onReturnPressed` / `Keys.onSpacePressed` -> toggle recording
- Add visible 2px focus ring using `activeFocus` state
**Settings.qml nav sidebar:**
- Add `activeFocusOnTab: true` to each nav item
- Add `Keys.onReturnPressed` / `Keys.onSpacePressed` -> select tab
- Add `Keys.onUpPressed` / `Keys.onDownPressed` -> move between tabs
- Visible focus ring on each nav item
**Settings.qml close button:**
- Add `activeFocusOnTab: true`
- Add `Keys.onReturnPressed` / `Keys.onSpacePressed` -> close
**All custom controls:**
- Ensure `activeFocusOnTab: true` on ModernSlider, ModernSwitch, ModernComboBox, ModernKeySequenceRecorder, GlowButton
- 2px focus ring visible when `activeFocus` is true
---
## Section 6: Reduced Motion Support
Three-layer system: OS detection, user toggle, animation conditionals.
**Config layer (`config.py`):**
- Add `"reduce_motion": false` to DEFAULT_SETTINGS
**OS detection (`main.py`):**
- On Windows startup, call `SystemParametersInfo(SPI_GETCLIENTAREAANIMATION)` via ctypes
- If OS says reduce motion, set config `reduce_motion: true` (user can override)
**Bridge layer (`bridge.py`):**
- Add `reduceMotion` Q_PROPERTY (bool, notify signal)
- Reads from config, exposed to QML as `ui.reduceMotion`
**QML conditionals:**
Overlay.qml animations (6 total):
- Shimmer: `running: !ui.reduceMotion`
- Gradient blobs shader: `visible: !ui.reduceMotion`, static fallback color
- Glow shader: `visible: !ui.reduceMotion`
- CRT shader: `visible: !ui.reduceMotion`
- Particle emitter: `enabled: !ui.reduceMotion`
- Rainbow wave shader: `visible: !ui.reduceMotion`
Loader.qml animations (2 total):
- Icon pulse: `running: !ui.reduceMotion`
- Progress shimmer: `running: !ui.reduceMotion`
**UI toggle:**
- Add "Reduce Motion" toggle in Settings.qml Visuals tab
- Bound to config `reduce_motion` via bridge
---
## Section 7: Remaining WCAG AAA Criteria
**7a. Status Messages (4.1.3):**
- Overlay status label gets `Accessible.role: Accessible.AlertMessage` (covered in Section 4)
**7b. Text Scaling (1.4.4, 1.4.10):**
- Already handled by existing `ui_scale` config property. No changes needed.
**7c. Page Titles (2.4.2):**
- Window titles set in Section 4 window-level properties.
**7d. Error Identification (3.3.1):**
- Not applicable. No user-submitted forms with validation.
---
## Files Changed (Summary)
| File | Changes |
|---|---|
| `src/ui/qml/SettingsStyle.qml` | Token updates + new tokens |
| `src/ui/qml/Overlay.qml` | Colors, accessibility, keyboard, reduced motion |
| `src/ui/qml/Settings.qml` | Colors, accessibility, keyboard, reduce motion toggle |
| `src/ui/qml/Loader.qml` | Colors, accessibility, reduced motion |
| `src/ui/qml/ModernSlider.qml` | Size, focus ring, accessibility |
| `src/ui/qml/ModernSwitch.qml` | I/O marks, border, accessibility |
| `src/ui/qml/ModernTextField.qml` | Placeholder color, focus, accessibility |
| `src/ui/qml/ModernComboBox.qml` | Border, arrow color, focus, accessibility |
| `src/ui/qml/ModernKeySequenceRecorder.qml` | Border, colors, focus, accessibility |
| `src/ui/qml/GlowButton.qml` | Text color, border, accessibility |
| `src/ui/qml/ModernSettingsSection.qml` | Accessibility properties |
| `src/ui/qml/ModernSettingsItem.qml` | Accessibility properties |
| `src/core/config.py` | Add `reduce_motion` default |
| `src/ui/bridge.py` | Add `reduceMotion` property |
| `main.py` | Windows reduced-motion OS detection |