Skip to content

fix(site): WCAG accessibility pass across all pages#155

Open
whoknowsla wants to merge 3 commits into
rohitg00:mainfrom
whoknowsla:fix/accessibility
Open

fix(site): WCAG accessibility pass across all pages#155
whoknowsla wants to merge 3 commits into
rohitg00:mainfrom
whoknowsla:fix/accessibility

Conversation

@whoknowsla
Copy link
Copy Markdown

Summary

Brings the static site (site/) up to WCAG 2.1 AA across all five HTML pages (index, catalog, lesson, glossary, prereqs). Visible changes are limited to slightly darker muted text and stronger borders on the header buttons; no layout or behavior regressions.

What changed, by WCAG criterion

Level A

  • 2.1.1 Keyboard — Home-page phase cards are now role="button" tabindex="0" with Enter/Space activation. Quiz options in the stage-based renderer switched from <div onclick> to real <button>s.
  • 2.4.3 / 4.1.2 — Phase modal gets role="dialog", aria-modal="true", aria-labelledby, a Tab focus trap, initial focus on the close button, and focus restore to the trigger on close.
  • 3.3.2 Labelssr-only labels added to the catalog search/phase/status controls and the glossary search.
  • 2.4.4 Link Purpose — Header GitHub link gets an aria-label on every page (was previously announced as just "…" while the star count loaded).
  • 4.1.2 — Lesson sidebar toggle syncs aria-expanded / aria-controls. Unreleased lessons in the modal no longer render as empty <a> (now a labeled <span>).

Level AA

  • 1.4.3 Contrast (Minimum)--ink-mute darkened on both themes to clear 4.5:1 against the body background.
  • 1.4.11 Non-text Contrast — New --rule-strong token (~3:1) used for borders on .theme-toggle, .search-toggle, .header-github.
  • 2.4.7 Focus Visible — Global :focus-visible outline so every link / button / role=button shows a focus ring.
  • 4.1.2 — Catalog sort headers are now <button>s with aria-sort cycling none → ascending → descending.
  • 4.1.3 Status Messagesrole="status" aria-live="polite" on catalog count, glossary count, lesson loader, quiz score, and quiz explanations.

Polish

  • <aside> regions on the lesson page get aria-labels.
  • <div class="toc-title"> on the home page promoted to <h2> for proper heading order.
  • Theme-toggle inner letter is aria-hidden so AT no longer reads "Toggle theme, N".
  • Decorative spinner / status dots / stat bars marked aria-hidden.
  • <main> on the lesson page gets tabindex="-1" so the skip-link target reliably receives focus.
  • Stripped leftover "bar" text from progress-bar spans.

Files

site/app.js        keyboard support, focus trap/restore, empty-<a> fix
site/catalog.html  form labels, button-wrapped sort headers, aria-sort, live count
site/glossary.html form label, aria-label on GitHub link, live count
site/index.html    dialog semantics, h2 toc-title, aria-labels, aria-hidden cleanup
site/lesson.html   sidebar aria-label/expanded, quiz <div>→<button>, live regions
site/prereqs.html  aria-label, type="button" on theme toggle
site/style.css     sr-only, global :focus-visible, --rule-strong, --ink-mute bump

Verified

  • Tabbed through every page — visible focus ring on all interactive elements.
  • Phase card → Enter opens modal with focus on close button; Tab/Shift+Tab stay trapped; Esc closes and restores focus to the originating card.
  • Catalog sort headers respond to Enter/Space and announce direction via aria-sort.
  • Lesson quiz options accept keyboard input; explanations and score announce via live regions.
  • Mobile sidebar toggle reflects expanded/collapsed state.
  • Both light and dark themes render cleanly with the stronger borders.

Adds keyboard support, dialog semantics, form labels, contrast bumps,
focus indicators, and live regions so the site meets WCAG 2.1 AA on
the static pages.

Highlights:
- Phase cards on index now keyboard-activatable (role/tabindex + Enter/Space)
- Phase modal gets role="dialog", focus trap, focus restore, initial focus
- Quiz options in the stage-based renderer switched from <div onclick> to <button>
- Catalog/glossary inputs get sr-only labels; catalog headers get aria-sort
- Live regions on result counts, quiz score, quiz explanations, lesson loader
- GitHub header link gets a real aria-label on every page
- Lesson sidebar toggle now syncs aria-expanded/aria-controls
- --ink-mute darkened to 4.5:1 (both themes); new --rule-strong (3:1) for
  component borders on theme-toggle/search-toggle/header-github
- Global :focus-visible ring; sr-only utility added
- Empty <a> for unreleased lessons replaced with a <span>
- Skip-link target (<main>) made programmatically focusable on lesson page
Copilot AI review requested due to automatic review settings May 24, 2026 02:31
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 24, 2026

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 2f5c53d0-500a-4f89-84c2-0cc72f634dfb

📥 Commits

Reviewing files that changed from the base of the PR and between 44d165e and 73a27e6.

📒 Files selected for processing (1)
  • site/app.js

📝 Walkthrough

Walkthrough

Accessibility updates across the site: CSS utilities and contrast, consistent header/theme ARIA, catalog button-based sorting, modal focus trapping/restoration and keyboard activation, glossary/live-region search, lesson sidebar and quiz ARIA wiring, and index modal/progress adjustments.

Changes

Accessibility & Focus Management

Layer / File(s) Summary
CSS contrast and accessibility utilities
site/style.css
Adjusted theme variables for contrast, added .sr-only, global :focus-visible ring, strengthened borders, and modal-lesson pending styling.
Header & theme accessibility across templates
site/index.html, site/catalog.html, site/glossary.html, site/lesson.html, site/prereqs.html
Add aria-label to header repo links, switch star-count placeholders to aria-hidden="true", and mark theme icon aria-hidden.
Catalog sorting UI and form accessibility
site/catalog.html
Refactor sortable headers to contain .sort-btn buttons, add sr-only labels and aria-controls on inputs, and update sorting click handling and aria-sort state updates.
Modal focus management and keyboard navigation
site/app.js
Phase rows gain button semantics and aria-labels; modal opens via click/Enter/Space with trigger passed to openModal; focus helpers added; openModal traps focus, locks scroll, and focuses close button; closeModal restores scroll and focus; copy feedback announced.
Glossary search UI and live regions
site/glossary.html
Glossary search input gets sr-only label, type="search", aria-controls, and the count becomes a live role="status" region.
Lesson sidebar toggle and quiz accessibility
site/lesson.html
Main gets tabindex="-1", loading indicator is a live region, sidebar toggle syncs aria-expanded, and quiz HTML uses role="group", labelled IDs, live-region explanations, and aria-hidden markers.
Index progress and modal structure
site/index.html
Progress bars rendered decorative and aria-hidden, TOC uses aria-labelledby, modal overlay and dialog ARIA updated, and copy-to-clipboard status live region added.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • ohuseynli
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and accurately describes the main objective of the PR: implementing WCAG 2.1 AA accessibility compliance across the site's pages.
Description check ✅ Passed The description is comprehensive and directly related to the changeset, detailing the accessibility improvements by WCAG criterion, affected files, and verification steps.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Improves site-wide accessibility and keyboard navigation, with emphasis on WCAG contrast and better screen-reader semantics for interactive UI.

Changes:

  • Adjusted theme tokens and component borders to improve contrast (incl. new --rule-strong).
  • Added global focus-visible ring styling and introduced an .sr-only utility class.
  • Enhanced ARIA semantics and keyboard support across pages (modal focus management, sortable table headers, quiz options as buttons).

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
site/style.css Adds .sr-only, global focus rings, stronger border token, and contrast tweaks.
site/prereqs.html Adds ARIA labeling and button types for header controls.
site/lesson.html Improves navigation semantics (sidebar toggle state), loading announcements, and quiz option accessibility.
site/index.html Improves document structure (headings/labels) and modal dialog semantics.
site/glossary.html Adds accessible search labeling and live count announcements.
site/catalog.html Makes sortable headers real buttons and improves filter/search labeling and announcements.
site/app.js Adds keyboard activation for phase rows and modal focus management (focus return + tab trapping).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread site/app.js Outdated
Comment thread site/index.html Outdated
Comment thread site/style.css
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
site/style.css (1)

140-140: ⚡ Quick win

Replace deprecated clip property with modern clip-path.

The clip property is deprecated. Since the codebase already uses clip-path elsewhere (lines 880, 884), update the .sr-only utility to use the modern equivalent for consistency and standards compliance.

♻️ Proposed fix using clip-path
-  clip: rect(0, 0, 0, 0);
+  clip-path: inset(50%);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@site/style.css` at line 140, The `.sr-only` utility uses the deprecated CSS
property `clip`; replace it with the modern equivalent by removing `clip:
rect(0, 0, 0, 0);` and adding a matching `clip-path: inset(50%);` (or `inset(0 0
0 0)` combined with appropriate positioning) to achieve the same visually-hidden
behavior; update the rule in the `.sr-only` selector so it matches the project's
existing `clip-path` usage and accessibility intent (see other uses around
`clip-path` on lines ~880/884 for style consistency).
site/lesson.html (1)

2700-2719: ⚡ Quick win

Consider updating quiz explanation content when showing for better screen reader support.

The quiz explanation live region (line 2713) contains static text rendered initially and is only made visible on line 2753 by changing display to 'block'. Some screen readers may not announce role="status" regions when only visibility changes without content updates.

For better cross-AT compatibility, consider one of these patterns:

  1. Render explanation empty initially, then insert text when showing
  2. Clear and re-insert text to force a content change event

Compare to the quiz score pattern (lines 2774-2776), which correctly updates textContent before showing—that will work reliably across screen readers.

Suggested refactor

Change line 2713 to render empty:

 if (q.explanation) {
-  html += '<div class="quiz-explanation" id="' + qid + '-exp" role="status" aria-live="polite">' + escapeHtml(q.explanation) + '</div>';
+  html += '<div class="quiz-explanation" id="' + qid + '-exp" role="status" aria-live="polite" data-explanation="' + escapeAttr(q.explanation) + '"></div>';
 }

Then update line 2753 to insert content:

 var exp = questionDiv.querySelector('.quiz-explanation');
-if (exp) exp.style.display = 'block';
+if (exp) {
+  exp.textContent = exp.getAttribute('data-explanation');
+  exp.style.display = 'block';
+}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@site/lesson.html` around lines 2700 - 2719, The quiz explanation div
currently renders the explanation text upfront (id constructed as qid + '-exp')
and later only toggles visibility, which may not trigger screen readers; change
the initial render in the quiz builder so the element with id qid + '-exp' is
created empty (no explanation text), and then in the code path that shows the
explanation (the place that sets display = 'block') first set the element's
textContent (or innerText) to the escaped explanation (use
escapeHtml(q.explanation)) so the live region receives a content update, then
make it visible—this mirrors how the quiz score is updated and ensures better AT
announcement behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@site/lesson.html`:
- Around line 2700-2719: The quiz explanation div currently renders the
explanation text upfront (id constructed as qid + '-exp') and later only toggles
visibility, which may not trigger screen readers; change the initial render in
the quiz builder so the element with id qid + '-exp' is created empty (no
explanation text), and then in the code path that shows the explanation (the
place that sets display = 'block') first set the element's textContent (or
innerText) to the escaped explanation (use escapeHtml(q.explanation)) so the
live region receives a content update, then make it visible—this mirrors how the
quiz score is updated and ensures better AT announcement behavior.

In `@site/style.css`:
- Line 140: The `.sr-only` utility uses the deprecated CSS property `clip`;
replace it with the modern equivalent by removing `clip: rect(0, 0, 0, 0);` and
adding a matching `clip-path: inset(50%);` (or `inset(0 0 0 0)` combined with
appropriate positioning) to achieve the same visually-hidden behavior; update
the rule in the `.sr-only` selector so it matches the project's existing
`clip-path` usage and accessibility intent (see other uses around `clip-path` on
lines ~880/884 for style consistency).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1204ed92-92bb-4f96-a9e9-317cef0bd12c

📥 Commits

Reviewing files that changed from the base of the PR and between 4415dc8 and 0604293.

📒 Files selected for processing (7)
  • site/app.js
  • site/catalog.html
  • site/glossary.html
  • site/index.html
  • site/lesson.html
  • site/prereqs.html
  • site/style.css

- app.js: switch focus-trap visibility check from offsetParent to
  getClientRects(); offsetParent is null for position:fixed elements
  even when they are visible and focusable
- index.html / app.js: drop ineffective aria-live on the copy button
  (its aria-label overrides textContent so AT never heard "Copied!");
  announce via a dedicated sr-only role=status region instead
- style.css: add modern clip-path: inset(50%) to .sr-only, keep clip
  as a legacy WebKit fallback
- lesson.html: render quiz-explanation empty and inject text on reveal
  so the live region fires a content-change announcement, not just a
  visibility flip
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@site/app.js`:
- Around line 381-388: The live-region text isn't being retriggered on rapid
repeated copies because the status element keeps the same text; update the copy
handler (the block that references status, revertTimer, btn, originalLabel) to
force a re-announcement by clearing status.textContent before setting it to
'Command copied to clipboard' (e.g., set status.textContent = '' then in the
next tick set the message, via requestAnimationFrame or setTimeout 0), ensure
you still clear any existing revertTimer and reset it to restore btn.textContent
and clear status after 1500ms so repeated clicks always produce a new AT
announcement.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 13a2bce7-cf89-4a37-b0f8-afb47f93162e

📥 Commits

Reviewing files that changed from the base of the PR and between 0604293 and 44d165e.

📒 Files selected for processing (4)
  • site/app.js
  • site/index.html
  • site/lesson.html
  • site/style.css

Comment thread site/app.js
Clear the status region before setting the message on the next animation
frame so screen readers announce each successful copy, not just the first.
Identical textContent updates are de-duped by AT otherwise.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants