7.7 KiB
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
.topbarin<header role="banner"> - Wrap
.contentin<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
.appNamefrom<div>to<h1>(keep existing class) - Change
.nowTitlefrom<div>to<h2>(keep existing class) - Change
.dockTitleelements from<div>to<h3>(keep existing class) - Change
.playlistHeaderfrom<div>to<h2>(keep existing class) - Convert info panel
.kvblocks from nested<div>to<dl>/<dt>/<dd>, styled with same.kv/.k/.vclasses
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 inupdateOverall() - Add
aria-label="Volume"to volume slider - Toggle
aria-expanded="true"/"false"onsubsBtn,speedBtn,chooseDropBtnwhen menus open/close - Toast already has
aria-live="polite"-- no change needed
Playlist rows (in playlist.ts)
- Set
role="listbox"andaria-label="Playlist"on the#listcontainer - Each row:
role="option",aria-selected="true"/"false", computedaria-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:
*: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: -2pxto stay within bounds - Sliders:
:focus-visible::-webkit-slider-thumbbox-shadow glow - Notes textarea: enhance existing
:focusborder 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
- Playlist rows:
tabindex="0",keydownEnter/Space to activate, Arrow Up/Down to navigate rows - Menu items (subtitle, speed, recent): Use
<button>elements ortabindex="0"withrole="menuitem". Enter/Space to activate, Arrow Up/Down to navigate, Escape to close and return focus to trigger zoomResetBtn: Change from<span>to<button>- 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-labels, 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 |