- 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/
219 lines
7.6 KiB
Markdown
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 |
|