Files
core-cooldown/docs/plans/2026-02-18-wcag-aaa-implementation.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

27 KiB
Raw Permalink Blame History

WCAG 2.2 AAA Implementation Plan

For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.

Goal: Achieve WCAG 2.2 AAA conformance across all frontend components while preserving the existing dark-theme visual identity.

Architecture: All changes are CSS + Svelte template only — no Rust backend changes. Theme tokens in app.css propagate through Tailwind's @theme system. Components use Svelte 5 runes ($state, $derived, $effect, $props). Accessibility patterns follow WAI-ARIA 1.2 (tablist, radiogroup, alertdialog, progressbar).

Tech Stack: Svelte 5, Tailwind CSS v4 (@theme tokens in CSS, no config file), TypeScript, Web Animations API (motion library)

Note: This project has no frontend test suite. Verification is done via npm run build (Vite build) and manual inspection. Each task ends with a build check and a commit.


Task 1: Theme Tokens & Global Styles (app.css)

Files:

  • Modify: break-timer/src/app.css (entire file)

This is the foundation — all subsequent tasks depend on these token changes.

Step 1: Update theme tokens

In app.css, inside the @theme { } block (lines 320):

  • Change --color-text-sec: #8a8a8a--color-text-sec: #a8a8a8 (7.28:1 on black)
  • Change --color-text-dim: #3a3a3a--color-text-dim: #5c5c5c (3.5:1 decorative)
  • Change --color-border: #222222--color-border: #3a3a3a (2.63:1 non-text)
  • Change --color-danger: #f85149--color-danger: #ff6b6b (7.41:1)
  • Add new token: --color-input-border: #444444;
  • Add new token: --color-surface-lt: #1e1e1e;

Step 2: Update body styles

In the html, body block (lines 2235):

  • Change user-select: none to only apply on drag regions:
    • REMOVE user-select: none; and -webkit-user-select: none; from body
    • ADD a new rule for drag regions only:
      [data-tauri-drag-region],
      [data-tauri-drag-region] * {
        user-select: none;
        -webkit-user-select: none;
      }
      
  • Add line-height: 1.625; (leading-relaxed) to body for AAA 1.4.8

Step 3: Enhance focus indicators

Replace the :focus-visible block (lines 7376) with:

:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
  box-shadow: 0 0 0 4px rgba(255, 255, 255, 0.3);
}

The box-shadow provides a white fallback when the accent color has low contrast against dark backgrounds.

Step 4: Add skip link styles

After the .sr-only block, add:

.skip-link {
  position: absolute;
  top: -100%;
  left: 50%;
  transform: translateX(-50%);
  z-index: 10000;
  padding: 8px 16px;
  background: var(--color-accent);
  color: #000;
  font-size: 13px;
  font-weight: 600;
  border-radius: 0 0 8px 8px;
  text-decoration: none;
  transition: top 0.15s ease;
}
.skip-link:focus {
  top: 0;
}

Step 5: Verify build

Run: cd break-timer && npm run build Expected: Build succeeds with no errors.

Step 6: Commit

git add break-timer/src/app.css
git commit -m "a11y: update theme tokens and global styles for WCAG AAA

- Bump --color-text-sec to #a8a8a8 (7.28:1 contrast)
- Bump --color-text-dim to #5c5c5c (3.5:1 decorative)
- Bump --color-border to #3a3a3a (2.63:1 non-text)
- Bump --color-danger to #ff6b6b (7.41:1)
- Add --color-input-border and --color-surface-lt tokens
- Add white shadow fallback on :focus-visible
- Add leading-relaxed default line-height
- Scope user-select:none to drag regions only
- Add skip-link focus styles"

Task 2: App Shell (App.svelte)

Files:

  • Modify: break-timer/src/App.svelte

Step 1: Add skip link

Inside the <main> element (line 87), add skip link as first child and an id target:

<main class="relative h-full bg-black">
  <a href="#main-content" class="skip-link">Skip to content</a>
  ...

Then on the zoom container <div> (line 92), add id="main-content":

<div
  id="main-content"
  class="relative h-full overflow-hidden origin-top-left"

Step 2: Add document title effect

After the existing focus management $effect (after line 76), add:

// WCAG 2.4.2: Document title reflects current view
$effect(() => {
  const viewNames: Record<string, string> = {
    dashboard: "Dashboard",
    breakScreen: "Break",
    settings: "Settings",
    stats: "Statistics",
  };
  document.title = `Core Cooldown — ${viewNames[effectiveView] ?? "Dashboard"}`;
});

Step 3: Verify build

Run: cd break-timer && npm run build

Step 4: Commit

git add break-timer/src/App.svelte
git commit -m "a11y: add skip link and dynamic document title for WCAG AAA"

Task 3: Titlebar (Titlebar.svelte)

Files:

  • Modify: break-timer/src/lib/components/Titlebar.svelte

Step 1: Wrap in header landmark

Change the outer <div> (line 8) to <header role="banner">.

Step 2: Enlarge traffic lights to meet 44px target size

Change each traffic light button from h-[15px] w-[15px] to h-[20px] w-[20px] visual size with min-h-[44px] min-w-[44px] hit area via padding. The trick: keep the visual circle at 20px but wrap in a 44px invisible tap area.

Replace each button's class. For example the Maximize button (line 27):

<button
  aria-label="Maximize"
  class="traffic-btn group/btn relative flex h-[44px] w-[44px] items-center justify-center"
  onclick={() => appWindow.toggleMaximize()}
>
  <span class="flex h-[20px] w-[20px] items-center justify-center rounded-full bg-[#27C93F] transition-all duration-150 group-hover/btn:brightness-110">
    <svg ...>
  </span>
</button>

Apply the same pattern to Minimize and Close buttons. The gap between buttons changes from gap-[8px] to gap-0 since the 44px buttons provide their own spacing.

Step 3: Verify build

Run: cd break-timer && npm run build

Step 4: Commit

git add break-timer/src/lib/components/Titlebar.svelte
git commit -m "a11y: add header landmark and enlarge traffic lights to 44px hit areas"

Task 4: ToggleSwitch (ToggleSwitch.svelte)

Files:

  • Modify: break-timer/src/lib/components/ToggleSwitch.svelte

Step 1: Enlarge to 52x28 and fix knob contrast

Change button dimensions from h-[24px] w-[48px] to h-[28px] w-[52px] with min-h-[44px] padding for hit area.

Change the knob span from h-[19px] w-[19px] to h-[22px] w-[22px]. Change translate-x for ON state from translate-x-[26px] to translate-x-[27px]. Change mt from mt-[2.5px] to mt-[3px]. Change OFF knob color from bg-[#444] to bg-[#666] (better contrast).

The button should have min-h-[44px] via a wrapper approach or padding.

Step 2: Verify build

Run: cd break-timer && npm run build

Step 3: Commit

git add break-timer/src/lib/components/ToggleSwitch.svelte
git commit -m "a11y: enlarge toggle switch and improve OFF knob contrast"

Task 5: Stepper (Stepper.svelte)

Files:

  • Modify: break-timer/src/lib/components/Stepper.svelte

Step 1: Enlarge buttons and fix contrast

Change both +/- button dimensions from h-7 w-7 (28px) to h-9 w-9 (36px) with min-h-[44px] min-w-[44px] padding.

Change bg-[#141414] to bg-[#1a1a1a] border border-[#3a3a3a] for better non-text contrast.

Change text-[#8a8a8a] to text-text-sec (uses updated theme token).

Step 2: Add keyboard hold-to-repeat

Add onkeydown handlers to both buttons that trigger the same startHold/stopHold logic for Enter, Space, ArrowUp, ArrowDown keys:

function handleKeydown(fn: () => void, e: KeyboardEvent) {
  if (["Enter", " ", "ArrowUp", "ArrowDown"].includes(e.key)) {
    e.preventDefault();
    startHold(e.key === "ArrowDown" || e.key === "ArrowUp" ? fn : fn);
  }
}

function handleKeyup(e: KeyboardEvent) {
  if (["Enter", " ", "ArrowUp", "ArrowDown"].includes(e.key)) {
    stopHold();
  }
}

Add onkeydown and onkeyup to both buttons. The decrease button uses ArrowDown for decrement, the increase button uses ArrowUp for increment.

Step 3: Verify build

Run: cd break-timer && npm run build

Step 4: Commit

git add break-timer/src/lib/components/Stepper.svelte
git commit -m "a11y: enlarge stepper buttons and add keyboard hold-to-repeat"

Task 6: Animation Actions (animate.ts)

Files:

  • Modify: break-timer/src/lib/utils/animate.ts

Step 1: Add keyboard support to pressable

In the pressable function (line 117), after the mousedown/mouseup/mouseleave listeners (lines 143145), add keydown/keyup for Enter and Space:

function onKeyDown(e: KeyboardEvent) {
  if (e.key === "Enter" || e.key === " ") {
    e.preventDefault();
    onDown();
  }
}

function onKeyUp(e: KeyboardEvent) {
  if (e.key === "Enter" || e.key === " ") {
    onUp();
  }
}

node.addEventListener("keydown", onKeyDown);
node.addEventListener("keyup", onKeyUp);

Update destroy() to remove these listeners too.

Step 2: Add focus support to glowHover

In the glowHover function (line 165), after mouseenter/mouseleave listeners (lines 201202), add focusin/focusout:

node.addEventListener("focusin", onEnter);
node.addEventListener("focusout", onLeave);

Update destroy() to remove these listeners too.

Step 3: Verify build

Run: cd break-timer && npm run build

Step 4: Commit

git add break-timer/src/lib/utils/animate.ts
git commit -m "a11y: add keyboard feedback to pressable and focus glow to glowHover"

Task 7: Dashboard (Dashboard.svelte)

Files:

  • Modify: break-timer/src/lib/components/Dashboard.svelte

Step 1: Replace hardcoded #8a8a8a with theme token

Replace all text-[#8a8a8a] with text-text-sec throughout the file. There are ~10 instances on lines 180, 208, 216, 234, 241, 256, 301, 329, 361.

Replace border-[#222] with border-border on the three bottom buttons (lines 302, 328, 360).

Step 2: Wrap bottom buttons in nav landmark

Around the three bottom action buttons (lines 296382), wrap in:

<nav aria-label="Main actions">
  <!-- Bottom left: start break now -->
  ...
  <!-- Bottom center: stats -->
  ...
  <!-- Bottom right: settings -->
  ...
</nav>

The three <div class="absolute bottom-5 ..."> blocks move inside the <nav>.

Step 3: Make natural break toast persistent on hover

Replace the simple timeout logic (lines 118126) with hover-aware persistence:

let toastHovering = $state(false);

$effect(() => {
  if ($timer.naturalBreakOccurred) {
    showNaturalBreakToast = true;
    if (naturalBreakToastTimeout) clearTimeout(naturalBreakToastTimeout);
    naturalBreakToastTimeout = setTimeout(() => {
      if (!toastHovering) showNaturalBreakToast = false;
    }, 5000);
  }
});

On the toast div (line 266), add:

onmouseenter={() => toastHovering = true}
onmouseleave={() => { toastHovering = false; showNaturalBreakToast = false; }}
onfocusin={() => toastHovering = true}
onfocusout={() => { toastHovering = false; showNaturalBreakToast = false; }}

Add a close button inside the toast and an Escape key handler.

Step 4: Add progressbar role to daily goal bar

On the goal progress bar div (line 235), add ARIA:

<div class="w-16 h-[2px] rounded-full overflow-hidden" style="background: #161616;"
  role="progressbar"
  aria-label="Daily goal progress"
  aria-valuemin={0}
  aria-valuemax={$config.daily_goal_breaks}
  aria-valuenow={dailyGoalProgress}
>

Step 5: Add sr-only text for pomodoro dots

After the pomodoro dots visual <div> (around line 192), add:

<span class="sr-only">
  Pomodoro cycle: session {$timer.pomodoroCyclePosition + 1} of {$timer.pomodoroTotalInCycle}
</span>

Step 6: Verify build

Run: cd break-timer && npm run build

Step 7: Commit

git add break-timer/src/lib/components/Dashboard.svelte
git commit -m "a11y: dashboard contrast, nav landmark, toast persistence, goal progressbar"

Task 8: Settings — Part 1: Headings & Structure (Settings.svelte)

Files:

  • Modify: break-timer/src/lib/components/Settings.svelte

This is a large file (1176 lines). Split into two commits for reviewability.

Step 1: Change all <h3> to <h2>

Replace every <h3 class="mb-4 text-[11px] ... with <h2 class="mb-4 text-[11px] ... and corresponding </h3> with </h2>. There are 17 instances at approximate lines: 163, 218, 280, 346, 435, 444, 498, 590, 668, 732, 825, 862, 906, 976(Working Hours has no heading — add one), 1078, 1119, 1138.

Step 2: Add id to headings and aria-labelledby to sections

Each <section> gets an aria-labelledby pointing to its heading's id. Pattern:

<section aria-labelledby="settings-timer" class="rounded-2xl p-5 ...">
  <h2 id="settings-timer" class="mb-4 ...">Timer</h2>

Do this for all 18 sections. Use ids: settings-timer, settings-pomodoro, settings-microbreaks, settings-breakscreen, settings-activities, settings-breathing, settings-behavior, settings-alerts, settings-sound, settings-idle, settings-presentation, settings-goals, settings-appearance, settings-workinghours, settings-minimode, settings-general, settings-shortcuts.

Step 3: Add missing heading for Working Hours section

The Working Hours section (line 975) has no <h3>/<h2>. Add:

<h2 id="settings-workinghours" class="mb-4 text-[11px] font-medium tracking-[0.15em] text-text-sec uppercase">
  Working Hours
</h2>

Step 4: Verify build

Run: cd break-timer && npm run build

Step 5: Commit

git add break-timer/src/lib/components/Settings.svelte
git commit -m "a11y: settings heading hierarchy (h3→h2) and section landmarks"

Task 9: Settings — Part 2: ARIA, Contrast, Sizes (Settings.svelte)

Files:

  • Modify: break-timer/src/lib/components/Settings.svelte

Step 1: Breathing pattern — radiogroup/radio

Wrap the breathing pattern buttons (line 464) in a role="radiogroup" container:

<div role="radiogroup" aria-label="Breathing pattern" class="flex flex-col gap-1.5">

Each breathing pattern button gets role="radio" and aria-checked:

<button
  role="radio"
  aria-checked={$config.breathing_pattern === bp.id}
  ...

Step 2: Sound preset — aria-pressed

Each sound preset button (line 709) gets aria-pressed:

<button
  aria-pressed={$config.sound_preset === preset}
  ...

Step 3: Replace hardcoded colors

  • All text-[#8a8a8a]text-text-sec (many instances throughout)
  • All bg-[#161616] dividers → bg-border (use --color-border token)
  • All border-[#161616] on inputs → border-border
  • All placeholder:text-[#2a2a2a]placeholder:text-[#555] (3.37:1)
  • All bg-[#141414] → use updated stepper component (already handled in Task 5)
  • Back button h-8 w-8h-10 w-10 min-h-[44px] min-w-[44px] (line 127)

Step 4: Reset button aria-live

Wrap the reset button text in an aria-live="polite" region, or add aria-live="polite" to the button itself so screen readers announce the confirmation state change:

<button
  aria-live="polite"
  ...
>
  {resetConfirming ? "Tap again to confirm reset" : "Reset to defaults"}
</button>

Step 5: Add abbreviation tags

For standalone unit abbreviations in setting descriptions, wrap with <abbr>:

  • "s" (seconds) → <abbr title="seconds">s</abbr>
  • "min"<abbr title="minutes">min</abbr>

Apply on first occurrence in: alert timing description (line 613), idle timeout (line 755), snooze duration (line 537).

Step 6: Add title tooltips on complex settings

Add title attributes on the section-level labels for Pomodoro, Smart breaks, Microbreaks, Breathing:

  • Pomodoro <h2>: title="Pomodoro technique alternates focused work sessions with short and long breaks"
  • Microbreaks <h2>: title="20-20-20 rule: every 20 minutes, look 20 feet away for 20 seconds"
  • Smart breaks <div>: title="Automatically counts time away from computer as a break"
  • Breathing guide <h2>: title="Visual breathing exercise during breaks to reduce stress"

Step 7: Verify build

Run: cd break-timer && npm run build

Step 8: Commit

git add break-timer/src/lib/components/Settings.svelte
git commit -m "a11y: settings ARIA patterns, contrast, abbreviations, and tooltips"

Task 10: StatsView — Tabs & Data Tables (StatsView.svelte)

Files:

  • Modify: break-timer/src/lib/components/StatsView.svelte

Step 1: Change all <h3> to <h2>

Replace all <h3> with <h2> and </h3> with </h2> (~12 instances).

Step 2: Implement tablist pattern

Replace the tab navigation (lines 297310):

<div role="tablist" aria-label="Statistics time range" class="flex gap-1 px-5 mb-3" use:fadeIn={{ duration: 0.3, y: 6 }}>
  {#each [["today", "Today"], ["weekly", "Weekly"], ["monthly", "Monthly"]] as [tab, label]}
    <button
      role="tab"
      aria-selected={activeTab === tab}
      aria-controls="tabpanel-{tab}"
      id="tab-{tab}"
      use:pressable
      class="rounded-lg px-4 py-2 min-h-[44px] text-[11px] tracking-wider uppercase transition-all duration-200
             {activeTab === tab
               ? 'bg-[#1a1a1a] text-white'
               : 'text-text-sec hover:text-white'}"
      onclick={() => activeTab = tab as any}
    >
      {label}
    </button>
  {/each}
</div>

Wrap each tab's content in a role="tabpanel":

{#if activeTab === "today"}
  <div role="tabpanel" id="tabpanel-today" aria-labelledby="tab-today">
    ...
  </div>

Same for weekly and monthly.

Step 3: Add sr-only data tables for 30-day chart and heatmap

After the 30-day chart canvas (around line 533), add:

{#if monthHistory.length > 0}
  <table class="sr-only">
    <caption>Break history for the last {monthHistory.length} days</caption>
    <thead>
      <tr><th>Date</th><th>Completed</th><th>Skipped</th></tr>
    </thead>
    <tbody>
      {#each monthHistory as day}
        <tr><td>{day.date}</td><td>{day.breaksCompleted}</td><td>{day.breaksSkipped}</td></tr>
      {/each}
    </tbody>
  </table>
{/if}

After the heatmap canvas (around line 559), add:

{#if monthHistory.length > 0}
  <table class="sr-only">
    <caption>Activity heatmap for the last {monthHistory.length} days</caption>
    <thead>
      <tr><th>Date</th><th>Breaks completed</th></tr>
    </thead>
    <tbody>
      {#each monthHistory as day}
        <tr><td>{day.date}</td><td>{day.breaksCompleted}</td></tr>
      {/each}
    </tbody>
  </table>
{/if}

Step 4: Replace hardcoded colors

  • All text-[#8a8a8a]text-text-sec
  • bg-[#161616] dividers → bg-border
  • Back button: h-8 w-8h-10 w-10 min-h-[44px] min-w-[44px]

Step 5: Verify build

Run: cd break-timer && npm run build

Step 6: Commit

git add break-timer/src/lib/components/StatsView.svelte
git commit -m "a11y: stats tablist pattern, sr-only data tables, contrast updates"

Task 11: BreakScreen (BreakScreen.svelte)

Files:

  • Modify: break-timer/src/lib/components/BreakScreen.svelte

Step 1: Add strict mode focus safety

When strict_mode is true and buttons are hidden, the focus trap has zero focusable elements. After the {#if showButtons} block (around line 353 for in-app, line 203 for standalone), add an else block:

{:else}
  <span tabindex="0" class="sr-only" aria-live="polite">
    Break in progress, please wait
  </span>
{/if}

This ensures the focus trap always has at least one focusable element.

Step 2: Fix breathing aria-live to only announce phase changes

The breathing guide <span aria-live="polite"> (lines 172 and 304) currently announces every countdown tick. Add a tracked variable that only updates on phase change:

In the script section, add:

let lastAnnouncedPhase = $state("");
let breathAnnouncement = $derived(
  breathPhase !== lastAnnouncedPhase
    ? (() => { lastAnnouncedPhase = breathPhase; return `${breathPhase}`; })()
    : ""
);

Actually, better approach — use a separate $effect and a dedicated announcement state:

let breathAnnouncement = $state("");
$effect(() => {
  // Only announce when the breathing phase name changes, not countdown ticks
  breathAnnouncement = breathPhase;
});

Then change the aria-live span to use a separate invisible span for SR announcements:

<span class="sr-only" aria-live="polite" aria-atomic="true">{breathAnnouncement}</span>

Keep the visible breathing text without aria-live (remove aria-live="polite" from the visible span).

Step 3: Replace hardcoded colors

  • text-[#8a8a8a]text-text-sec
  • border-[#222]border-border

Step 4: Verify build

Run: cd break-timer && npm run build

Step 5: Commit

git add break-timer/src/lib/components/BreakScreen.svelte
git commit -m "a11y: break screen strict-mode focus safety and breathing phase announcements"

Task 12: Celebration Toast Persistence (Celebration.svelte)

Files:

  • Modify: break-timer/src/lib/components/Celebration.svelte

Step 1: Add hover/focus-aware auto-dismiss

Replace the CSS-driven 3.5s fade with JS-controlled timing. Add state:

let milestoneHovering = $state(false);
let goalHovering = $state(false);

For the .celebration-overlay div, add:

onmouseenter={() => milestoneHovering = true}
onmouseleave={() => milestoneHovering = false}
onfocusin={() => milestoneHovering = true}
onfocusout={() => milestoneHovering = false}

Remove pointer-events: none from .celebration-overlay and .goal-overlay styles.

Add $effect blocks that manage visibility based on hover state — when not hovering, set a timeout to add a CSS class that triggers fade-out.

Add a close button (sr-only label "Dismiss notification") to both overlays, and an Escape key handler.

Step 2: Verify build

Run: cd break-timer && npm run build

Step 3: Commit

git add break-timer/src/lib/components/Celebration.svelte
git commit -m "a11y: celebration overlays persist on hover/focus with dismiss controls"

Task 13: MiniTimer Contrast (MiniTimer.svelte)

Files:

  • Modify: break-timer/src/lib/components/MiniTimer.svelte

Step 1: Fix paused text contrast

Change line 338: color: {state === 'paused' ? '#555' : '#fff'} to color: {state === 'paused' ? '#a8a8a8' : '#fff'}.

Replace #8a8a8a with the text-sec token value #a8a8a8 on line 344 for pomodoro indicator.

Step 2: Verify build

Run: cd break-timer && npm run build

Step 3: Commit

git add break-timer/src/lib/components/MiniTimer.svelte
git commit -m "a11y: fix mini timer paused text contrast for AAA"

Task 14: MicrobreakOverlay (MicrobreakOverlay.svelte)

Files:

  • Modify: break-timer/src/lib/components/MicrobreakOverlay.svelte

Step 1: Add alertdialog role, heading, and label

Change the outer .microbreak-card div (line 41):

<div class="microbreak-card" role="alertdialog" aria-label="Microbreak" aria-describedby="microbreak-msg">
  <h2 class="sr-only">Microbreak</h2>
  <div class="flex items-center gap-3 mb-2">
    ...
    <span id="microbreak-msg" class="text-[15px] font-medium text-white">
      Look away — 20 feet for {timeRemaining}s
    </span>
  </div>

Replace text-[#8a8a8a] with text-text-sec.

Step 2: Verify build

Run: cd break-timer && npm run build

Step 3: Commit

git add break-timer/src/lib/components/MicrobreakOverlay.svelte
git commit -m "a11y: microbreak overlay gets alertdialog role and heading"

Task 15: BreakOverlay (BreakOverlay.svelte)

Files:

  • Modify: break-timer/src/lib/components/BreakOverlay.svelte

Step 1: Add alertdialog role, heading, and label

Change the outer div (line 39):

<div
  role="alertdialog"
  aria-label="Break in progress"
  class="fixed inset-0 flex flex-col items-center justify-center"
  style="background: rgba(0, 0, 0, {$config.backdrop_opacity});"
>
  <h2 class="sr-only">Break in Progress</h2>
  <p class="text-[16px] font-medium text-white mb-4">Break in progress</p>

Step 2: Verify build

Run: cd break-timer && npm run build

Step 3: Commit

git add break-timer/src/lib/components/BreakOverlay.svelte
git commit -m "a11y: break overlay gets alertdialog role and heading"

Task 16: ColorPicker (ColorPicker.svelte)

Files:

  • Modify: break-timer/src/lib/components/ColorPicker.svelte

Step 1: Enlarge swatches and add aria-pressed

Change swatch buttons (line 247) from h-[22px] w-[22px] to h-[28px] w-[28px] with a min-h-[44px] min-w-[44px] clickable area (via padding or wrapper).

Add aria-pressed={value === color} to each swatch button.

Change the flex container gap from gap-[6px] to gap-[8px] to accommodate larger swatches.

Replace text-[#8a8a8a] with text-text-sec.

Step 2: Verify build

Run: cd break-timer && npm run build

Step 3: Commit

git add break-timer/src/lib/components/ColorPicker.svelte
git commit -m "a11y: enlarge color swatches, add aria-pressed"

Task 17: ActivityManager Target Sizes (ActivityManager.svelte)

Files:

  • Modify: break-timer/src/lib/components/ActivityManager.svelte

Step 1: Enlarge star/remove buttons

Find all favorite (★) and remove (✕) buttons. Change from any w-8 h-8 / w-7 h-7 to w-9 h-9 min-h-[44px] min-w-[44px].

Replace text-[#8a8a8a] with text-text-sec.

Step 2: Verify build

Run: cd break-timer && npm run build

Step 3: Commit

git add break-timer/src/lib/components/ActivityManager.svelte
git commit -m "a11y: enlarge activity manager action buttons to 44px targets"

Task 18: Final Build Verification & Cleanup

Step 1: Full build

Run: cd break-timer && npm run build Expected: Build succeeds with zero errors.

Step 2: Verify all theme token propagation

Search for any remaining hardcoded #8a8a8a in Svelte files — there should be none (all replaced with text-text-sec or inline #a8a8a8).

Run: grep -r "#8a8a8a" break-timer/src/ — should return zero results.

Step 3: Verify no remaining h3 headings in Settings/Stats

Run: grep -n "<h3" break-timer/src/lib/components/Settings.svelte break-timer/src/lib/components/StatsView.svelte — should return zero results.

Step 4: Final commit if any cleanup needed

git add -A break-timer/src/
git commit -m "a11y: final cleanup pass for WCAG 2.2 AAA compliance"

Dependency Graph

Task 1 (app.css tokens)
  ├── Task 2 (App.svelte)
  ├── Task 3 (Titlebar)
  ├── Task 4 (ToggleSwitch)
  ├── Task 5 (Stepper)
  ├── Task 6 (animate.ts)
  ├── Task 7 (Dashboard) ← depends on Task 6
  ├── Task 8 (Settings part 1)
  ├── Task 9 (Settings part 2) ← depends on Task 8
  ├── Task 10 (StatsView)
  ├── Task 11 (BreakScreen)
  ├── Task 12 (Celebration)
  ├── Task 13 (MiniTimer)
  ├── Task 14 (MicrobreakOverlay)
  ├── Task 15 (BreakOverlay)
  ├── Task 16 (ColorPicker)
  └── Task 17 (ActivityManager)
Task 18 (Final verification) ← depends on all above

Tasks 217 are mostly independent of each other (except 7 depends on 6, and 9 depends on 8). They all depend on Task 1 being done first.