Files
core-cooldown/docs/plans/2026-02-18-wcag-aaa-design.md
Your Name 2f7aa074bc v0.2.0: WCAG 2.2 AAA accessibility + toggle fix + version bump
- Upgrade accessibility from WCAG 2.1 AA to WCAG 2.2 AAA conformance
- 42 accessibility fixes across 18 frontend components
- Enhanced contrast ratios (7:1 body text, 4.5:1 large text, 3:1 non-text)
- 44px minimum touch/click targets on all interactive elements
- Full WAI-ARIA 1.2: tablist, radiogroup, alertdialog, progressbar, switch
- Screen-reader-only data tables behind chart, dynamic page titles
- Skip navigation link, semantic heading hierarchy (h1 > h2)
- Celebration popups persist on hover/focus with keyboard dismiss
- Fix ToggleSwitch visual height (44px hit area, 28px visual track)
- Update README with detailed WCAG 2.2 AAA accessibility section
- Include WCAG design doc and implementation plan in docs/plans/
2026-02-18 19:18:15 +02:00

219 lines
7.6 KiB
Markdown

# WCAG 2.2 AAA Compliance Design
**Date:** 2026-02-18
**Scope:** `break-timer/` (frontend + CSS only, no Rust backend changes)
**Goal:** Achieve WCAG 2.2 AAA conformance while preserving the existing dark-theme visual identity.
## Audit Summary
42 issues found (8 Critical, 14 Major, 20 Minor) across 28 source files. The app already has solid AA foundations: focus indicators, reduced motion support, forced colors mode, ARIA roles on custom widgets, screen-reader text, and keyboard support for complex components.
## Design Decisions
1. **Secondary text color:** `#8a8a8a` -> `#a8a8a8` (7.28:1 ratio, minimal visual change)
2. **Target sizes:** Enlarge controls to 44px (visual + padding), including 20px traffic lights, 28px swatches, 36px steppers
3. **Auto-dismiss toasts:** Persist until dismissed when user hovers/focuses; auto-fade only if untouched
---
## Section 1: Color & Contrast System
### Theme Token Changes (`app.css`)
| Token | Current | New | Ratio on #000 |
|-------|---------|-----|---------------|
| `--color-text-sec` | `#8a8a8a` | `#a8a8a8` | 7.28:1 |
| `--color-text-dim` | `#3a3a3a` | `#5c5c5c` | 3.5:1 (decorative) |
| `--color-border` | `#222222` | `#3a3a3a` | 2.63:1 (non-text) |
| New: `--color-input-border` | — | `#444444` | 3.14:1 |
| New: `--color-surface-lt` | — | `#1e1e1e` | 1.28:1 (bg-to-bg) |
### Hardcoded Color Replacements
- All `text-[#8a8a8a]` -> `text-text-sec` (Tailwind theme token)
- All `border-[#222]` -> `border-border`
- All `border-[#161616]` -> `border-[#333]` (card dividers: 3:1)
- All `bg-[#141414]` (stepper bg) -> `bg-[#1a1a1a]` with `border border-[#3a3a3a]`
- Toggle OFF knob: `#444` -> `#666`
- Mini paused text: `#555` -> `#a8a8a8`
- Placeholder text: `#2a2a2a` -> `#555` (3.37:1)
- Danger color: `#f85149` -> `#ff6b6b` (7.41:1)
### Focus Indicator Safety
When accent color is too dark, add white outer shadow fallback:
```css
:focus-visible {
outline: 2px solid var(--color-accent);
outline-offset: 2px;
box-shadow: 0 0 0 4px rgba(255,255,255,0.3);
}
```
---
## Section 2: Target Size Enlargement
| Component | Current | New Visual | Hit Area |
|-----------|---------|-----------|----------|
| Titlebar traffic lights | 15x15px | 20x20px | 44x44px (padding) |
| Color swatches | 22x22px | 28x28px | 44px spacing |
| Stepper +/- buttons | 28x28px | 36x36px | 44x44px (padding) |
| Toggle switch | 48x24px | 52x28px | 52x44px (padding) |
| Back button | 32x32px | 40x40px | 44x44px |
| Stats tab buttons | ~60x30px | ~60x40px | 44px height |
| Activity star/remove | 32x32px | 36x36px | 44x44px (padding) |
| Time range buttons | 32x32px | 36x36px | 44x44px (padding) |
Strategy: Use `min-h-[44px] min-w-[44px]` on interactive elements with padding for visual sizing.
---
## Section 3: Heading Hierarchy & Document Structure
### Heading Fixes
- Each view keeps `<h1 class="sr-only" tabindex="-1">`
- Settings/Stats: Change all `<h3>` to `<h2>`
- Working Hours section: Add missing heading
- BreakScreen `<h2>` stays correct
### Landmark Regions
- Titlebar: Wrap in `<header role="banner">`
- Dashboard bottom buttons: Wrap in `<nav aria-label="Main actions">`
- Stats tab bar: `<nav>` with `role="tablist"` / `role="tab"` / `role="tabpanel"`
- All sections: Add `aria-labelledby` pointing to heading `id`
### Document Title
Add `$effect` in `App.svelte` to set `document.title = "Core Cooldown - ${viewName}"` on view change.
---
## Section 4: Keyboard & Focus Fixes
### C5: Strict Mode Keyboard Trap
When `strict_mode` is true and buttons are hidden, render a visually hidden focusable `<span tabindex="0">` with sr-only text "Break in progress, please wait" so focus trap always has one element.
### Stepper Keyboard Hold-to-Repeat
Add `onkeydown` handler that starts repeat on held ArrowUp/ArrowDown/Enter/Space.
### `pressable` Keyboard Feedback
Add `keydown`/`keyup` listeners for Enter/Space to trigger same scale animation.
### `glowHover` Keyboard Focus
Add `focusin`/`focusout` listeners alongside `mouseenter`/`mouseleave`.
### Missing ARIA States
- Sound presets: `aria-pressed={$config.sound_preset === preset}`
- Breathing patterns: `role="radiogroup"` wrapper, `role="radio"` + `aria-checked` on buttons
- Color swatches: `aria-pressed={value === color}`
---
## Section 5: Timing & Notification Control
### Toast Persistence
All auto-dismissing elements (natural break toast, celebration overlay, goal badge):
1. Track `hovering` via `mouseenter/leave` + `focusin/focusout`
2. Auto-dismiss timer pauses while hovering
3. Close button appears (sr-only "Dismiss notification")
4. Escape key dismisses
5. `role="alert"` + `aria-live="assertive"` kept
### Breathing Guide Noise Fix
Change breathing `aria-live` span to only announce phase changes (Inhale/Hold/Exhale), not countdown ticks. Track `lastAnnouncedPhase` and update live text only on phase name change.
---
## Section 6: ARIA Patterns & Semantic Enrichment
### Section Accessible Names
Add `id` to each heading, `aria-labelledby` on parent `<section>`.
### Stats Tab Pattern
- `role="tablist"` wrapper
- `role="tab"` + `aria-selected` + `aria-controls` on each tab
- `role="tabpanel"` + `id` + `aria-labelledby` on panels
### Missing Data Tables
Add sr-only `<table>` for 30-day chart and heatmap (matching existing 7-day pattern).
### Additional ARIA
- Daily goal bar: `role="progressbar"` + aria-value attributes
- BreakOverlay: `role="alertdialog"` + `aria-label` + `<h2>`
- MicrobreakOverlay: `role="alertdialog"` + `aria-label` + heading
- Reset button: `aria-live="polite"` for confirmation state
---
## Section 7: Visual Presentation (1.4.8)
### Line Spacing
Default `leading-relaxed` (1.625) on body. Override with `leading-none` only on countdown numerics.
### Text Selection
Remove global `user-select: none`. Keep only on drag regions and decorative elements.
### Skip Navigation
Add skip link as first child of `<main>`: `<a href="#main-content" class="sr-only focus:not-sr-only">Skip to content</a>`.
---
## Section 8: Supplementary
### Abbreviations
Add `<abbr>` tags for standalone "s", "min", "h", "m" units. Add `title` on first use of Pomodoro, 20-20-20 in Settings.
### Help Tooltips
Add `title` attributes on complex settings (Pomodoro, Smart breaks, Microbreaks, Breathing).
### Known Limitations
- Canvas chart text spacing: Not fixable (Canvas API limitation). Sr-only data tables provide equivalent access.
- Timer interruptions: Pause button effectively suppresses all breaks. No separate DND mode needed.
---
## Files Affected
| File | Change Scope |
|------|-------------|
| `app.css` | Tokens, line-height, user-select, skip link, focus ring |
| `App.svelte` | Skip link, document title |
| `Titlebar.svelte` | Larger traffic lights, header landmark |
| `Dashboard.svelte` | Contrast, nav landmark, toast persistence, pomodoro alt text, goal progressbar |
| `BreakScreen.svelte` | Contrast, strict-mode focus, breathing aria-live fix |
| `Settings.svelte` | h2 headings, section labels, contrast, sizes, radio groups, abbr, title, reset aria-live |
| `StatsView.svelte` | h2 headings, tablist, data tables, contrast |
| `ToggleSwitch.svelte` | Larger (52x28), knob contrast |
| `Stepper.svelte` | Larger (36px), keyboard hold-repeat, contrast |
| `ColorPicker.svelte` | Larger swatches, aria-pressed, contrast |
| `FontSelector.svelte` | Contrast |
| `TimeSpinner.svelte` | Contrast |
| `ActivityManager.svelte` | Larger buttons, contrast, scroll fix |
| `MiniTimer.svelte` | Contrast |
| `MicrobreakOverlay.svelte` | Heading, role, label |
| `BreakOverlay.svelte` | Heading, role, label |
| `Celebration.svelte` | Toast persistence |
| `animate.ts` | pressable keyboard, glowHover focus |