Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WPJztrVGbwic2xTG7G9fjM
9.8 KiB
Accessibility Audit Design — intotheeast
Date: 2026-06-20
Standard: WCAG 2.1 Level AA
Scope: All Twig templates in user/themes/intotheeast/templates/, CSS tokens, and inline JS
1. Audit Results
Failures (must fix)
| ID | Criterion | Severity | Where | Issue |
|---|---|---|---|---|
| F1 | 2.4.1 Bypass Blocks | High | Every page | No skip-to-main link — keyboard users must tab through site header on every page load |
| F2 | 1.4.3 Contrast Minimum | High | tokens.css |
--color-ink-muted (#7A7268) is 3.74:1 on --color-paper and 3.44:1 on --color-canvas — fails 4.5:1 AA for small text. Used for timestamps, location labels, weather spans, and stat labels |
| F3 | 1.4.3 Contrast Minimum | High | tokens.css |
--color-accent (#2A8C73) is 4.30:1 on paper and 3.95:1 on canvas — fails 4.5:1 AA. Used as link text color on journal permalinks, back-pill anchors, and feed-map link |
| F4 | 4.1.2 Name/Role/Value | High | trip.html.twig |
Filter buttons (All content / Journal / Stories) have no aria-pressed — the active filter is communicated only via CSS class, invisible to screen readers |
| F5 | 4.1.2 Name/Role/Value | High | trip.html.twig |
Stats and Cycling toggle buttons have no aria-expanded or aria-controls — collapsed/expanded state is invisible to screen readers |
| F6 | 2.1.1 Keyboard | Medium | All three feed templates | Photo strip is scroll-snap only; no keyboard navigation. Slides cannot be advanced by keyboard users |
| F7 | 4.1.2 Name/Role/Value | Medium | gpx-manager.html.twig |
Each table row has a bare "Delete" button — a screen reader hears "Delete, Delete, Delete" with no way to distinguish which file is targeted |
| F8 | 1.1.1 Non-text Content | Medium | entry.html.twig |
When the lightbox is open, the enlarged <img> has alt="" — the displayed photo has no accessible description |
Passes (no changes needed)
<html lang="en">,<header>,<main>,<footer>landmark structure ✓<nav aria-label="Main navigation">✓aria-current="page"on active nav link ✓- Global
:focus-visiblerule with--color-accentoutline ✓ prefers-reduced-motionblock covering all animations ✓- Lightbox:
role="dialog",aria-modal="true",aria-label="Photo viewer", labelled close/prev/next buttons ✓ aria-hidden="true"on photo-strip dots, story hero overlay, story scroll cue ✓<time datetime="…">on all entry dates ✓--color-ink(#EDE8DF): 14.53:1 on paper ✓--color-ink-2(#B8B0A4): 8.26:1 on paper ✓- Story nav title
aria-hidden="true"(decorative scroll-driven element) ✓ - Back-to-top button
aria-label="Back to top"✓ - Hero image
altwithhero_alt ?? page.titlefallback ✓
2. Fixes
Task 1 — Skip link + main landmark id
Files: user/themes/intotheeast/templates/partials/base.html.twig, user/themes/intotheeast/css/style.css
Add a visually-hidden skip link as the first focusable element in the page, before the site header. On :focus-visible it snaps to the top-left corner of the viewport. Add id="main-content" to the existing <main class="site-main"> element so the link has a valid target.
Skip-link CSS: off-screen at rest (e.g. position: absolute; left: -10000px), snaps to top: 0; left: 0 on :focus-visible. Styled with accent color to match the site's existing focus ring aesthetic.
Task 2 — Color token contrast fixes
Files: user/themes/intotheeast/css/tokens.css
Two token values fail WCAG 1.4.3. Replace both:
| Token | Current | Replacement | Ratio on paper | Ratio on canvas |
|---|---|---|---|---|
--color-ink-muted |
#7A7268 | #90887E | 5.07:1 ✓ | 4.66:1 ✓ |
--color-accent |
#2A8C73 | #2E9880 | 5.00:1 ✓ | 4.59:1 ✓ |
--color-accent-hover |
#236655 | #287A68 | 3.58:1 ✓ (non-text) | — |
--color-accent-hover is used only for hover/active states, so the 3:1 non-text contrast criterion (1.4.11) applies rather than 4.5:1. #287A68 passes 3:1.
These are purely token changes — no template or layout changes required.
Task 3 — ARIA states for filter and toggle buttons
Files: user/themes/intotheeast/templates/trip.html.twig
Filter buttons (F4):
In the template, add aria-pressed="true" to the initially-active All content button and aria-pressed="false" to the other two. In the existing filter JS block (the trip-filter-btn click handler), toggle aria-pressed alongside is-active:
document.querySelectorAll('.trip-filter-btn').forEach(function(btn) {
btn.setAttribute('aria-pressed', btn === activeBtn ? 'true' : 'false');
});
Stats/Cycling toggles (F5):
Add aria-expanded="false" and aria-controls="trip-stats-block" to the Stats button. Add aria-expanded="false" and aria-controls="trip-cycling-block" to the Cycling button. Add the matching id attributes to the panels they control (id="trip-stats-block" already exists; add id="trip-cycling-block" to the cycling panel). In the toggle JS, set aria-expanded="true" when the panel is shown, "false" when hidden.
No new elements needed — only attribute additions to existing markup and existing JS handlers.
Task 4 — Photo strip keyboard navigation
Files: user/themes/intotheeast/templates/partials/base.html.twig (the dot-sync JS IIFE)
For each photo strip with more than one slide, inject a <button class="strip-prev" aria-label="Previous photo"> and <button class="strip-next" aria-label="Next photo"> as siblings to the strip after the dots. The buttons are hidden when the strip has only one slide (data-slides="1").
Clicking prev/next scrolls the strip by one slide width via scrollBy. The existing dot-sync scroll listener already updates dot state, so dots stay in sync automatically.
The strip container gains role="region" and aria-label="Photo strip" to group it as a named region for screen reader navigation.
CSS for the buttons: minimal, positioned relative to the strip, styled as teal chevrons matching the site palette. Hidden via display:none when data-slides="1".
The strip container itself does NOT get tabindex="0" — the injected buttons are the keyboard entry points, which is cleaner than making a scroll container focusable.
Task 5 — GPX delete button names + lightbox alt text
Files: user/themes/intotheeast/templates/gpx-manager.html.twig, user/themes/intotheeast/templates/entry.html.twig
GPX delete buttons (F7):
The delete buttons are built in the JS file-list renderer. Change the button label from Delete to Delete ${f.filename}:
td.innerHTML = `<button class="gpx-delete" data-filename="${f.filename}">Delete ${f.filename}</button>`;
The filename already appears in the adjacent <td>, so this adds redundancy for screen readers while not disturbing the visual layout. Alternatively, use aria-label="Delete ${f.filename}" and keep the visible text as Delete — either approach satisfies 4.1.2.
Use aria-label: keeps visible text short (Delete), accessible name specific (Delete 2026-03-25-tokyo.gpx).
Lightbox alt text (F8):
The lightbox open function already copies data-alt from the thumbnail. The fix is ensuring data-alt is populated with the thumbnail's alt attribute (which is entry.title — the entry title) and that the full-size <img> inside the lightbox receives it on open.
In the existing lightbox open JS: when setting the src of the lightbox <img>, also set its alt from the triggering thumbnail's alt attribute.
Task 6 — axe-core Playwright regression tests
Files: tests/ui/accessibility.spec.js (new), package.json
Add @axe-core/playwright as a devDependency. Create tests/ui/accessibility.spec.js that runs an axe accessibility scan on the following pages:
/(home)/trips/japan-korea-2026(trip page, with filter bar and stats)/trips/japan-korea-2026/dailies(journal feed with map)- One entry page (use a known demo slug)
/trips(trips archive)
Configuration: fail on critical and serious violations only. Log moderate and minor findings as warnings without failing. This matches realistic ongoing CI practice — the fixes in Tasks 1–5 should bring the site to zero critical/serious violations.
Each test uses the existing chromium project from playwright.config.js with the existing auth setup.
3. What is NOT in scope
- WCAG AAA criteria (e.g. 1.4.6 Enhanced Contrast at 7:1)
- Map marker keyboard navigation — MapLibre GL has built-in keyboard support for map pan/zoom; marker focus is a complex interaction pattern beyond the current scope. Deferred.
- Story page shortcode heading hierarchy — enforcing heading structure in author-written content is a content authoring concern, not a template concern
post-form.html.twig— the form is admin-only and used by Mischa alone; functional accessibility for this page is inherently self-tested
4. Testing approach
- Task 1–5: Manual verification by loading the page, tabbing through with keyboard, and checking AT output with a screen reader or browser accessibility tree inspector
- Task 6: Automated axe-core scan catches regressions after future template changes; run as part of
npx playwright test - Playwright tests must load demo data (
make demo-load) before running, consistent with existing test setup
5. File map
| File | Changed by |
|---|---|
user/themes/intotheeast/templates/partials/base.html.twig |
Tasks 1, 4 |
user/themes/intotheeast/css/style.css |
Task 1 |
user/themes/intotheeast/css/tokens.css |
Task 2 |
user/themes/intotheeast/templates/trip.html.twig |
Task 3 |
user/themes/intotheeast/templates/gpx-manager.html.twig |
Task 5 |
user/themes/intotheeast/templates/entry.html.twig |
Task 5 |
tests/ui/accessibility.spec.js |
Task 6 (new) |
package.json |
Task 6 |