docs: add WCAG 2.2 AAA remediation design
This commit is contained in:
184
docs/plans/2026-02-19-wcag-aaa-design.md
Normal file
184
docs/plans/2026-02-19-wcag-aaa-design.md
Normal file
@@ -0,0 +1,184 @@
|
||||
# WCAG 2.2 AAA Remediation Design
|
||||
|
||||
**Goal:** Bring TutorialVault to full WCAG 2.2 AAA compliance using surgical, in-place edits that preserve the Cold Open aesthetic.
|
||||
|
||||
**Approach:** Edit existing files directly. No new modules or abstractions. Fix each audit finding with the minimum code change. Contrast values raised just enough to hit 7:1 while keeping the cool, muted hierarchy.
|
||||
|
||||
**Constraints:**
|
||||
- Preserve the Cold Open visual identity (dark slate, steel blue accent, borderless surfaces)
|
||||
- Raise muted text contrast minimally to hit AAA 7:1
|
||||
- Focus rings use the existing accent color, subtle but compliant
|
||||
- No new dependencies
|
||||
|
||||
---
|
||||
|
||||
## 1. Semantic HTML & Landmarks
|
||||
|
||||
**File: `src/index.html`**
|
||||
|
||||
- Add `lang="en"` to `<html>`
|
||||
- Wrap `.topbar` in `<header role="banner">`
|
||||
- Wrap `.content` in `<main role="main">`
|
||||
- Add `role="region" aria-label="Video player"` to the left `.panel`
|
||||
- Add `role="region" aria-label="Playlist"` to the right `.panel`
|
||||
- Add `role="complementary" aria-label="Details"` to the `.dock`
|
||||
- Change `.appName` from `<div>` to `<h1>` (keep existing class)
|
||||
- Change `.nowTitle` from `<div>` to `<h2>` (keep existing class)
|
||||
- Change `.dockTitle` elements from `<div>` to `<h3>` (keep existing class)
|
||||
- Change `.playlistHeader` from `<div>` to `<h2>` (keep existing class)
|
||||
- Convert info panel `.kv` blocks from nested `<div>` to `<dl>`/`<dt>`/`<dd>`, styled with same `.kv`/`.k`/`.v` classes
|
||||
|
||||
No visual change. Headings inherit their existing class styles. Landmarks are invisible.
|
||||
|
||||
---
|
||||
|
||||
## 2. Text Alternatives & ARIA
|
||||
|
||||
### Icon-only buttons get `aria-label`
|
||||
|
||||
| Button | `aria-label` |
|
||||
|---|---|
|
||||
| `zoomOutBtn` | "Zoom out" |
|
||||
| `zoomInBtn` | "Zoom in" |
|
||||
| `zoomResetBtn` (change `<span>` to `<button>`) | "Reset zoom" |
|
||||
| `chooseDropBtn` | "Recent folders" |
|
||||
| `resetProgBtn` | "Reset progress" |
|
||||
| `refreshBtn` | "Reload folder" |
|
||||
| `winMinBtn` | "Minimize" |
|
||||
| `winMaxBtn` | "Maximize" |
|
||||
| `winCloseBtn` | "Close" |
|
||||
| `prevBtn` | "Previous video" |
|
||||
| `playPauseBtn` | "Play" (dynamically toggled to "Pause") |
|
||||
| `nextBtn` | "Next video" |
|
||||
| `subsBtn` | "Subtitles" |
|
||||
| `fsBtn` | "Toggle fullscreen" |
|
||||
|
||||
### Other ARIA additions
|
||||
|
||||
- Add `aria-hidden="true"` to all decorative `<i>` icons inside labeled buttons
|
||||
- Add `aria-label="Video player"` to `<video>`
|
||||
- Add `role="progressbar"`, `aria-valuenow`, `aria-valuemin="0"`, `aria-valuemax="100"`, `aria-label="Overall folder progress"` to `.progressBar`. Update dynamically in `updateOverall()`
|
||||
- Add `aria-label="Volume"` to volume slider
|
||||
- Toggle `aria-expanded="true"/"false"` on `subsBtn`, `speedBtn`, `chooseDropBtn` when menus open/close
|
||||
- Toast already has `aria-live="polite"` -- no change needed
|
||||
|
||||
### Playlist rows (in `playlist.ts`)
|
||||
|
||||
- Set `role="listbox"` and `aria-label="Playlist"` on the `#list` container
|
||||
- Each row: `role="option"`, `aria-selected="true"/"false"`, computed `aria-label` (e.g. "03. Video Title - 5:30 / 12:00 - Done")
|
||||
|
||||
---
|
||||
|
||||
## 3. Color Contrast (AAA 7:1)
|
||||
|
||||
Targeted CSS custom property adjustments in `:root`. Hierarchy preserved, just shifted up.
|
||||
|
||||
| Token | Current value | New value | Ratio vs #151821 |
|
||||
|---|---|---|---|
|
||||
| `--textMuted` | `rgba(148,162,192,.55)` ~3.6:1 | `rgba(160,174,204,.72)` ~7.2:1 | AAA pass |
|
||||
| `--textDim` | `rgba(118,132,168,.38)` ~1.8:1 | `rgba(158,174,208,.68)` ~7.1:1 | AAA pass |
|
||||
| `.tagline` color | `rgba(148,162,192,.62)` ~3.7:1 | Use `--textMuted` | AAA pass |
|
||||
| `.notes::placeholder` | `rgba(148,162,192,.40)` ~2.2:1 | `rgba(155,170,200,.65)` ~5.5:1 | Exempt but improved |
|
||||
| Time `/` separator | `rgba(165,172,196,.65)` | `rgba(175,185,210,.78)` ~7.1:1 | AAA pass |
|
||||
| `--icon` | `rgba(148,162,195,.48)` | `rgba(160,175,210,.62)` | Non-text 3:1 pass |
|
||||
|
||||
---
|
||||
|
||||
## 4. Focus Indicators (AAA 2.4.13)
|
||||
|
||||
Global `:focus-visible` rule in `main.css`:
|
||||
|
||||
```css
|
||||
*:focus-visible {
|
||||
outline: 2px solid rgba(136,164,196,.65);
|
||||
outline-offset: 2px;
|
||||
border-radius: inherit;
|
||||
}
|
||||
```
|
||||
|
||||
Component refinements:
|
||||
- Buttons: outline around button shape (default behavior)
|
||||
- Playlist rows: `outline-offset: -2px` to stay within bounds
|
||||
- Sliders: `:focus-visible::-webkit-slider-thumb` box-shadow glow
|
||||
- Notes textarea: enhance existing `:focus` border to 3:1 contrast
|
||||
- Switch labels: outline around entire label
|
||||
- Menu items: background highlight + outline on focus
|
||||
|
||||
`rgba(136,164,196,.65)` vs `#151821` gives ~5.5:1 -- passes the 3:1 requirement for focus indicators.
|
||||
|
||||
---
|
||||
|
||||
## 5. Keyboard Accessibility
|
||||
|
||||
### Interactive `<div>` elements become keyboard-accessible
|
||||
|
||||
1. **Playlist rows**: `tabindex="0"`, `keydown` Enter/Space to activate, Arrow Up/Down to navigate rows
|
||||
2. **Menu items** (subtitle, speed, recent): Use `<button>` elements or `tabindex="0"` with `role="menuitem"`. Enter/Space to activate, Arrow Up/Down to navigate, Escape to close and return focus to trigger
|
||||
3. **`zoomResetBtn`**: Change from `<span>` to `<button>`
|
||||
4. **Resize dividers**: `tabindex="0"`, `role="separator"`, `aria-valuenow`, `aria-orientation="vertical"`. Arrow Left/Right to resize in 2% increments
|
||||
|
||||
### Menu keyboard pattern (subtitles, speed, recent)
|
||||
|
||||
- Trigger button: `aria-haspopup="true"`, `aria-expanded="false"/"true"`
|
||||
- On open: focus moves to first menu item
|
||||
- Arrow Up/Down: navigate items
|
||||
- Enter/Space: activate item
|
||||
- Escape: close menu, return focus to trigger
|
||||
- Tab: close menu
|
||||
|
||||
### Playlist keyboard reorder (WCAG 2.5.7)
|
||||
|
||||
- Focused row: Alt+ArrowUp / Alt+ArrowDown moves it
|
||||
- Small move-up/move-down `<button>` elements appear on row focus/hover (right side, before tag)
|
||||
- After move, focus follows the moved row to its new position
|
||||
- A live region announces "Moved to position X"
|
||||
|
||||
---
|
||||
|
||||
## 6. Remaining AAA Criteria
|
||||
|
||||
### Dynamic page title (2.4.2)
|
||||
|
||||
Update `document.title` in `loadIndex()` and `onLibraryLoaded()`:
|
||||
- Playing: `"Video Title - TutorialVault"`
|
||||
- No video: `"TutorialVault - Open a folder"`
|
||||
|
||||
### Abbreviations (3.1.4)
|
||||
|
||||
Wrap abbreviations in info panel with `<abbr title="...">`:
|
||||
- ETA -> `<abbr title="Estimated Time of Arrival">ETA</abbr>`
|
||||
- FPS -> `<abbr title="Frames Per Second">FPS</abbr>`
|
||||
- kbps/Mbps -> `<abbr title="kilobits per second">kbps</abbr>`
|
||||
|
||||
Update `updateInfoPanel()` and `refreshCurrentVideoMeta()` in `ui.ts`.
|
||||
|
||||
### Target size (2.5.5 AAA -- 44x44px)
|
||||
|
||||
Expand hit areas of small buttons using padding + negative margin:
|
||||
- `.zoomBtn` (28x28): add padding to reach 44x44 effective, negative margin to keep visual layout
|
||||
- `.winBtn` (30x30): same technique
|
||||
- `.dropRemove` (24x24): same technique
|
||||
|
||||
Visual size stays the same; click/touch area grows.
|
||||
|
||||
### Reduced motion (2.3.3)
|
||||
|
||||
Already handled -- `prefers-reduced-motion` media query disables all animations/transitions. No change needed.
|
||||
|
||||
---
|
||||
|
||||
## Files Modified
|
||||
|
||||
| File | Changes |
|
||||
|---|---|
|
||||
| `src/index.html` | `lang`, landmarks, headings, `aria-label`s, `aria-hidden`, `<button>` for zoomReset, `<dl>` for info panel, `role`/`aria` on progress bar and video |
|
||||
| `src/styles/main.css` | `:root` contrast values, `:focus-visible` rules, hit area expansion for small buttons |
|
||||
| `src/styles/player.css` | Focus styles for sliders and control buttons |
|
||||
| `src/styles/playlist.css` | Focus styles for rows, move button styling |
|
||||
| `src/styles/panels.css` | Focus styles for notes, divider roles |
|
||||
| `src/styles/components.css` | Focus styles for menu items, tooltip improvements |
|
||||
| `src/playlist.ts` | `role="listbox"`, row `role="option"`, `tabindex`, keyboard nav, move buttons, Alt+Arrow reorder, live region |
|
||||
| `src/player.ts` | `aria-expanded` on speed button, menu keyboard nav, dynamic `aria-label` on play/pause, Escape handler |
|
||||
| `src/subtitles.ts` | `aria-expanded` on subs button, menu items as buttons, keyboard nav, Escape handler |
|
||||
| `src/ui.ts` | `aria-expanded` on recent dropdown, menu keyboard nav, Escape handler, divider keyboard resize, progress bar ARIA updates, dynamic `document.title`, abbreviation wrapping |
|
||||
| `src/main.ts` | Dynamic `document.title` updates |
|
||||
Reference in New Issue
Block a user