- 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/
7.6 KiB
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
- Secondary text color:
#8a8a8a->#a8a8a8(7.28:1 ratio, minimal visual change) - Target sizes: Enlarge controls to 44px (visual + padding), including 20px traffic lights, 28px swatches, 36px steppers
- 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]withborder 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:
: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>withrole="tablist"/role="tab"/role="tabpanel" - All sections: Add
aria-labelledbypointing to headingid
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-checkedon 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):
- Track
hoveringviamouseenter/leave+focusin/focusout - Auto-dismiss timer pauses while hovering
- Close button appears (sr-only "Dismiss notification")
- Escape key dismisses
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"wrapperrole="tab"+aria-selected+aria-controlson each tabrole="tabpanel"+id+aria-labelledbyon 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 |