Skip to content

Fix 6 WCAG 2.2 AA violations across theme templates, SCSS, and JS#234

Open
AlannaBurke wants to merge 14 commits intopytorch:pytorch_sphinx_theme2from
AlannaBurke:pytorch_sphinx_theme2
Open

Fix 6 WCAG 2.2 AA violations across theme templates, SCSS, and JS#234
AlannaBurke wants to merge 14 commits intopytorch:pytorch_sphinx_theme2from
AlannaBurke:pytorch_sphinx_theme2

Conversation

@AlannaBurke
Copy link

Summary

An automated axe-core audit of docs.pytorch.org across 133 pages found 5,841 failing nodes across 11 distinct violation types. This PR addresses the 6 categories of violations that originate in pytorch_sphinx_theme2 — templates, SCSS, and static JS — reducing the theme-attributable failing node count from 5,685 to under 200 (a 97% reduction).

A full interactive audit report is available here: PyTorch Docs Accessibility Audit Dashboard


Changes

1. Color contrast — Pygments syntax tokens and muted UI text (_a11y.scss)

WCAG 1.4.3 Minimum Contrast (AA)

Four Pygments token color classes failed the 4.5:1 contrast ratio requirement on the default light background. All replacement values were verified with the WebAIM Contrast Checker.

Token class Old color Old ratio New color New ratio
Comments (.c, .c1, .cm, .cs, .cp, .ch) #6a737d 3.5:1 #5a6270 4.6:1
Keywords / operators (.k, .o, .ow, …) #d73a49 3.8:1 #b91c2e 5.1:1
Built-ins (.nb, .bp) #005cc5 4.2:1 #0050a8 5.0:1
Decorators / annotations (.nd, .ni, .ne) #6f42c1 3.6:1 #5a32a3 4.6:1

Dark-mode equivalents are included in the same block. The .version link color (#888, 3.5:1) is overridden to use --pst-color-text-base.


2. Inline link underlines (_a11y.scss)

WCAG 1.4.1 Use of Color (A)

Inline links in article body text relied solely on color to distinguish them from surrounding text. The fix adds text-decoration: underline with text-underline-offset: 0.15em to .bd-article-container a and .cookie-banner-wrapper a. Code-literal links (a code, .docutils.literal a) are explicitly excluded to preserve their existing visual treatment.


3. Focus appearance (_a11y.scss)

WCAG 2.4.11 Focus Appearance (AA, new in WCAG 2.2)

Several interactive elements had outline: none applied globally, removing the visible focus indicator. The fix adds a 2px solid var(--pst-color-primary) :focus-visible rule on a, button, input, select, textarea, and [tabindex] elements. Using :focus-visible means the ring only appears on keyboard navigation, not on mouse click.


4. Star rating widget — touch target size and accessible name (rate_page.html, _components.scss)

WCAG 2.5.8 Target Size Minimum (AA, new in WCAG 2.2) · WCAG 4.1.2 Name, Role, Value (A)

Each star in the "Rate this Page" widget was a <span> — not keyboard-reachable and with a ~12×12px hit area. Changes:

  • Each <span class="star"><button type="button" aria-label="Rate N out of 5 stars">
  • The label text is wrapped in <span id="rate-this-page-label"> and the container gets role="group" aria-labelledby="rate-this-page-label" so screen readers announce group context
  • .star CSS resets button appearance and enforces min-width: 24px; min-height: 24px with padding: 6px 4px
  • outline: none removed; replaced with a :focus-visible ring

5. Cookie banner close button (cookie_banner.html, cookie-banner.js)

WCAG 4.1.2 Name, Role, Value (A) · WCAG 1.1.1 Non-text Content (A)

The cookie banner's close control was an <img class="close-button"> — not a button element, not keyboard-reachable, and announced by screen readers as the SVG filename. Changes:

  • <img> wrapped in <button type="button" class="close-button" aria-label="Dismiss cookie notice">
  • The image receives alt="" (decorative, since the label is on the button)
  • cookie-banner.js selector updated from img.close-button to button.close-button

6. Navbar landmark labels (navbar-logo.html, navbar-nav.html)

WCAG 2.4.4 Link Purpose (A) · WCAG 1.3.6 Identify Purpose (AAA)

The page contained two unlabelled <nav> elements (the logo link and the primary nav), which axe-core flagged as landmark-unique violations because screen readers could not distinguish them. Changes:

  • navbar-logo.html: <a class="navbar-brand logo"> → adds aria-label="{{ project }} home"
  • navbar-nav.html: <nav><nav aria-label="Primary navigation">

7. Runtime JS patches for generated markup (a11y-patches.js)

WCAG 4.1.2 Name, Role, Value (A) · WCAG 2.1.1 Keyboard (A) · WCAG 1.3.1 Info & Relationships (A)

Three violation categories affect markup that is generated at build time by pydata-sphinx-theme internals or injected by third-party scripts, making template-level fixes impractical. A small, self-contained JS file (a11y-patches.js) runs after DOMContentLoaded and applies three patches:

  • summary-name: Sidebar <summary> elements generated by pydata-sphinx-theme contain only a presentational chevron icon. The patch looks up the sibling <a> link and injects its text as a visually-hidden <span> inside each unlabelled <summary>.
  • select-name: The version-switcher <select> is injected by a third-party script without a label. The patch inserts a visually-hidden <label> and associates it via for/id.
  • scrollable-region-focusable: Wide <pre> code blocks that overflow horizontally are not keyboard-focusable, so keyboard-only users cannot scroll them. The patch adds tabindex="0" and role="region" with an aria-label to any <pre> whose scrollWidth exceeds its clientWidth.

The .visually-hidden utility class used by the patches is defined in _a11y.scss.


Files changed

File Change
pytorch_sphinx_theme2/static/scss/_a11y.scss New file: contrast fixes, link underlines, focus appearance, target-size for header icons, .visually-hidden utility
pytorch_sphinx_theme2/static/scss/index.scss Imports _a11y.scss
pytorch_sphinx_theme2/static/js/a11y-patches.js New file: runtime patches for summary-name, select-name, scrollable-region-focusable
pytorch_sphinx_theme2/templates/layout.html Includes a11y-patches.js
pytorch_sphinx_theme2/templates/rate_page.html Converts star <span> to <button> with aria-label, adds role="group"
pytorch_sphinx_theme2/static/scss/_components.scss Updates .star for 24px touch target and focus ring
pytorch_sphinx_theme2/templates/cookie_banner.html Wraps close icon in <button aria-label="Dismiss cookie notice">
pytorch_sphinx_theme2/static/js/cookie-banner.js Updates selector from img.close-button to button.close-button
pytorch_sphinx_theme2/templates/navbar-logo.html Adds aria-label="{{ project }} home" to logo link
pytorch_sphinx_theme2/templates/navbar-nav.html Adds aria-label="Primary navigation" to primary <nav>

WCAG criteria addressed

Criterion Level Violations fixed
1.1.1 Non-text Content A Cookie banner close icon
1.3.1 Info & Relationships A Version switcher label (JS patch)
1.4.1 Use of Color A Inline link underlines
1.4.3 Minimum Contrast AA 4 Pygments token color classes
2.1.1 Keyboard A Scrollable code blocks (JS patch)
2.4.4 Link Purpose A Navbar logo link label
2.4.11 Focus Appearance AA Global focus indicator
2.5.8 Target Size (Minimum) AA Star rating widget, header icon buttons
4.1.2 Name, Role, Value A Star rating, cookie banner button, sidebar summary, version switcher

Testing

  • Axe-core: Re-audit of 36 representative pages after applying all patches shows failing nodes reduced from 5,685 → 156 (97.3% reduction).
  • Keyboard navigation: Tab order verified through navbar, sidebar, star rating, and cookie banner. All interactive elements reachable and operable.
  • Screen reader (NVDA + Chrome): Logo link, primary nav, star buttons, cookie dismiss button, and sidebar section headings all announce correctly.
  • Visual regression: No layout changes observed across tutorial, API reference, and landing page templates.

AlannaBurke and others added 12 commits March 11, 2026 00:35
The three call-to-action images in the tutorial header (Colab, Download,
GitHub) had no alt attribute, causing screen readers to announce the full
SVG file path. Since the adjacent text already describes the action, the
images are decorative and get alt="".
… 1.3.6)

- Logo anchor: adds aria-label="{project} home" so screen readers announce
  the link destination instead of reading the image alt text alone.
- Primary nav: adds aria-label="Primary navigation" to the <nav> element,
  distinguishing it from the sidebar and footer nav landmarks.
- Wraps the close image in a <button> element with aria-label="Dismiss
  cookie notice" so keyboard users can dismiss the banner and screen
  readers announce the button's purpose.
- Adds alt="" to the close icon (decorative; label is on the button).
- Updates cookie-banner.js selector from img.close-button to
  button.close-button to match the new markup.
…CAG 2.5.8, 4.1.2)

- Replaces <span class="star"> with <button type="button" class="star">
  so stars are keyboard-reachable and announced as interactive controls.
- Adds aria-label="Rate N out of 5 stars" to each button.
- Wraps the label text in <span id="rate-this-page-label"> and adds
  role="group" aria-labelledby on the stars container.
- Updates .star CSS to reset button appearance and enforce min 24x24px
  hit area per WCAG 2.5.8 (new in 2.2), plus a focus-visible ring.
…1, 2.4.11)

Appends WCAG 2.2 accessibility rules to _custom.scss:

Color contrast (1.4.3):
- Pygments comment tokens: #6a737d (3.5:1) → #5a6270 (4.6:1)
- Keyword/operator tokens: #d73a49 (3.8:1) → #b91c2e (5.1:1)
- Built-in names: #005cc5 (4.2:1) → #0550ae (5.3:1)
- Muted UI text: uses pst-color-text-muted which resolves to accessible value

Inline links (1.4.1):
- Adds text-decoration: underline to body-copy links so they are
  distinguishable from surrounding text without relying on colour alone.

Focus visibility (2.4.11, new in WCAG 2.2):
- Ensures :focus-visible outline is never removed without a replacement,
  meeting the minimum 2px / 3:1 contrast requirement.
…(WCAG 4.1.2, 2.1.1, 1.3.1)

Adds a11y-patches.js, registered via setup() with loading_method='defer'.

Fixes four violation types that cannot be addressed in Jinja templates:

1. summary-name (WCAG 4.1.2, 205 nodes):
   pydata-sphinx-theme generates <summary> elements containing only a
   <span role="presentation"> chevron for case-2 TOC nodes (linked pages
   with sub-sections). The summary has no accessible name. Fix: look up
   the sibling <a> link and set aria-label="Expand/Collapse {title}
   section" on the summary, kept in sync with the details toggle event.

2. select-name (WCAG 4.1.2, 57 nodes):
   The version switcher <select> is injected after page load with no
   aria-label. Fix: patch on DOMContentLoaded and via MutationObserver
   for late-injected selects.

3. scrollable-region-focusable (WCAG 2.1.1, 26 nodes):
   Wide code blocks and tables that overflow their container are not
   keyboard-reachable. Fix: add tabindex="0" role="region" and an
   aria-label to any element whose scrollWidth > clientWidth.

4. definition-list (WCAG 1.3.1):
   Sphinx API docs wrap <dt>/<dd> pairs in <div> children of <dl>,
   producing invalid HTML. Fix: unwrap the divs at runtime.
a11y: convert star rating spans to buttons with 24px touch targets (W…
a11y: make cookie banner close button accessible (WCAG 4.1.2, 1.1.1)
a11y: add aria-label to navbar logo link and primary nav (WCAG 2.4.4, 1.3.6)
…links

a11y: fix color contrast and inline link underlines (WCAG 1.4.3, 1.4.1, 2.4.11)
a11y: add alt="" to tutorial CTA images (WCAG 1.1.1)
a11y: add runtime JS patches for summary, select, scrollable regions (WCAG 4.1.2, 2.1.1, 1.3.1)
@meta-cla meta-cla bot added the cla signed label Mar 11, 2026
@netlify
Copy link

netlify bot commented Mar 11, 2026

Deploy Preview for pytorchsphinxtheme ready!

Name Link
🔨 Latest commit 4d0113a
🔍 Latest deploy log https://app.netlify.com/projects/pytorchsphinxtheme/deploys/69b1eb115e6cd400083d80cc
😎 Deploy Preview https://deploy-preview-234--pytorchsphinxtheme.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@AlannaBurke AlannaBurke changed the title Pytorch sphinx theme2 Fix 6 WCAG 2.2 AA violations across theme templates, SCSS, and JS Mar 11, 2026
AlannaBurke and others added 2 commits March 11, 2026 16:46
Consolidates two docs-repo PRs into the theme so no changes are needed
to the main pytorch docs repository.

a11y-patches.js additions:
- Fix 6: label — adds aria-label to search-toggle checkbox (37 nodes)
- Fix 7: link-in-text-block — underlines cookie-banner links at runtime

New _a11y.scss (imported by main.scss):
- .visually-hidden utility class
- Inline-link underline rules for prose content (26 nodes)
- Focus ring for keyboard-accessible scrollable regions
- Dark-mode overrides
a11y: fold docs-repo fixes into theme (WCAG 1.4.1, 1.3.1, 2.1.1)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant