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