# 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 |