From 8a99ec97ac2f03c8efac2ea3d37e668ede43232f Mon Sep 17 00:00:00 2001 From: Jon Reades Date: Thu, 17 Sep 2026 20:49:23 +0100 Subject: [PATCH] Fix axe scrollable-region-focusable violation on code blocks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Quarto's theme CSS sets div.sourceCode { overflow: auto; }, making any code block wider than its container horizontally scrollable, but that div has no tabindex — a keyboard-only user can't focus it to scroll (WCAG 2.1.1, axe rule scrollable-region-focusable). Post-render script now adds tabindex="0" to every sourceCode block. Co-Authored-By: Claude Sonnet 5 --- scripts/post.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/scripts/post.py b/scripts/post.py index f1fb6142..9576ad7c 100644 --- a/scripts/post.py +++ b/scripts/post.py @@ -45,6 +45,19 @@ re.compile(r'(]*?) role="navigation"'), ] +# Quarto's theme CSS sets `div.sourceCode { overflow: auto; }`, making any +# code block wider than its container horizontally scrollable. That div has +# no tabindex, so a keyboard-only user can't focus it to scroll — WCAG 2.1.1 +# ("scrollable region must have keyboard access", axe rule +# scrollable-region-focusable). Whether a given block actually overflows +# depends on rendered width/font, which isn't knowable from the static HTML, +# so tabindex="0" is added to every sourceCode block; this is the standard +# remediation pattern for CSS-scrollable containers and is harmless on +# blocks that never overflow (just one extra, no-op tab stop). +TABINDEX_PATTERNS = [ + re.compile(r'
None: site = Path(output_dir) @@ -58,10 +71,12 @@ def label_nav_landmarks(output_dir: str) -> None: text = pattern.sub(lambda m: m.group(0) + label_attr, text) for pattern in ROLE_BUTTON_PATTERNS: text = pattern.sub(r'\1 role="button" tabindex="0"', text) + for pattern in TABINDEX_PATTERNS: + text = pattern.sub(lambda m: m.group(0) + ' tabindex="0"', text) if text != original: html_file.write_text(text, encoding="utf-8") n_files += 1 - print(f"[post.py] Added nav aria-labels to {n_files} HTML file(s) in {site}") + print(f"[post.py] Added nav aria-labels/roles to {n_files} HTML file(s) in {site}") _output_dir = os.environ.get("QUARTO_PROJECT_OUTPUT_DIR", "_site")