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

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

  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:

: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