Files
tutorialvault/docs/plans/2026-02-19-wcag-aaa-design.md
2026-02-19 15:54:04 +02:00

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 .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:

*: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-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