Skip to content

Accessibility on phones and iPads: contrast, touch targets, settings layout, screen-reader labels - #192

Merged
anantham merged 3 commits into
mainfrom
claude/mobile-a11y
Sep 29, 2026
Merged

anantham merged 3 commits into
mainfrom
claude/mobile-a11y

Conversation

@anantham

@anantham anantham commented Sep 25, 2026 •

Copy link
Copy Markdown
Owner

Owner request: make the mobile and iPad UI work well, with accessibility.

Audit (before)

Playwright with axe-core (WCAG 2.2 AA) covered the library, reader, text-selection sheet and settings at phone 390×844 / 844×390 and iPad 820×1180 / 1180×820, in light and dark mode, with touch emulation.

  • 68 color-contrast violations. The main cause is text-blue-500 on white at 3.76:1; the minimum is 4.5:1.
  • 48 links distinguishable by color alone, and 1 dropdown with no label.
  • About 45 controls under 44 px in settings, and 9–15 per screen elsewhere. The Original/English toggle was 24 px.
  • Form fields under 16 px. iOS Safari zooms the page when one is focused and leaves it zoomed.
  • Phone settings layout: a fixed 191 px sidebar left the form 165 px wide, cutting off the model search.
  • Keyboard and screen readers: a clickable div over a canvas (the oscilloscope minimap), mode toggles that didn't announce their state, and modal overlays with no dialog semantics or focus management.

Changes

  • Contrast: links and value read-outs use blue-700 / dark:blue-400, and inline links are underlined. Dark-mode greys on tabs, toggles and labels are fixed, and Export uses green-700.
  • Touch targets:
    • The pointer-coarse:min-h-11 Tailwind variant gives controls 44 px on touch screens only, so desktop density is unchanged.
    • A coarse-pointer rule in index.css gives form fields 16 px text (no iOS zoom) and a 44 px height.
  • Settings on phones: below sm, a labelled native section picker replaces the sidebar (iOS shows its picker wheel). On larger screens the sidebar is a nav landmark with aria-current and aria-expanded. The modal uses dvh height so iOS toolbars don't clip it.
  • Modal dialogs (useModalDialog, WAI-ARIA dialog pattern): Settings, the mobile version picker and the illustration dialog each:
    • move focus inside when they open;
    • keep Tab and Shift+Tab inside, and pull focus back if it has escaped to the page behind;
    • close on Escape, and return focus to the control that opened them.
  • Screen-reader labels:
    • The version dropdown and delete buttons are labelled, and the mode toggles have aria-pressed.
    • The oscilloscope minimap is a labelled button with a decorative canvas.
    • The mobile version picker uses full-row radio labels and respects the home-indicator safe area.
    • The comment field has a visible focus ring.
  • Phone layout: the settings gear shares the chapter row instead of taking its own row. "Loading chapters…" no longer pulses, which fixed a contrast dip and removes motion.

Gate

tests/e2e/mobile-a11y.spec.ts:

  • Matrix: 5 screens × 4 devices × 2 color schemes = 40 cases. 6 are skipped because the mobile version picker only exists below md, leaving 34 applicable. Each requires:
    • zero axe violations;
    • no horizontal overflow;
    • no zooming fields;
    • targets of at least 44 px, with WCAG 2.5.8's exceptions: links inside running text, sliders, and radio buttons counted with their labels.
  • Keyboard (3 cases): Settings on iPad landscape and on phone, and the version picker on phone. Each opens the dialog with the keyboard and checks that focus is inside, stays inside through a full Tab and Shift+Tab cycle, and returns to the opener after Escape. All 3 fail on ac2acc1 and pass now.

Putting back one old link color fails the matrix. It's a local gate, because Playwright isn't in CI yet (T0.4). Run it with npx playwright test tests/e2e/mobile-a11y.spec.ts (37 pass, 6 skipped).

Review fix

An independent review of ac2acc1 found that the dialogs were marked aria-modal but only handled Escape. Commit 7bb5217 adds useModalDialog and its tests. The unit tests cover focus entry, wrapping in both directions, recapturing escaped focus and focus return; the focus-return test was mutation-checked. The review also corrected the applicable-case count to 34.

Verification

  • tsc and the Playwright tsconfig: clean. eslint: 0 errors. vite build: pass.
  • vitest run: 9,609 passed, 1 failed. The failure is the known Node-22-only indrasNetImageProvider.test.ts case.
  • The existing mobile-selection-affordance and chapter-change-perf specs pass.

Limits

  • This container only has Chromium. Real iOS Safari and iPadOS weren't run; Safari's input zoom is handled by the 16 px rule rather than observed.
  • An iPad with a trackpad reports a fine pointer, so it gets the desktop sizes.
  • The settings panels were checked on the default Providers panel only. Other panels benefit from the global field rules but weren't individually audited.

🤖 Generated with Claude Code

https://claude.ai/code/session_011d1MbSabs9nA9d9w8pxP1W

… and iPads

Audited with Playwright + axe-core (WCAG 2.2 AA) on phone and iPad sizes,
portrait and landscape, light and dark, with touch emulation. Fixes:

- Contrast: text-blue-500 links/values (3.76:1) -> blue-700/blue-400, inline
  links underlined; dark-mode greys on tabs, toggles and labels; Export green.
- Touch targets: pointer-coarse:min-h-11 (44px) on reader, library, settings
  and footer controls; coarse-pointer CSS gives form fields 16px text (no iOS
  focus zoom) and 44px height.
- Settings on phones: native section picker instead of a 191px sidebar that
  left the form 165px wide; dialog semantics, aria-current/aria-expanded,
  Escape to close, dvh height for iOS toolbars.
- Screen readers: labelled version select and delete buttons, aria-pressed on
  mode toggles, oscilloscope minimap is a labelled button (was a clickable
  div over a canvas), mobile version picker is a dialog with full-row labels.
- Phones: the settings gear shares the chapter row instead of its own row.

tests/e2e/mobile-a11y.spec.ts gates all of it (zero axe violations, no
overflow, no zooming fields, 44px targets with WCAG exceptions).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011d1MbSabs9nA9d9w8pxP1W
@vercel

vercel Bot commented Sep 25, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
lexicon-forge Ready Ready Preview Sep 26, 2026 6:13pm UTC

Review of #192: Settings and the mobile version picker were marked
aria-modal but only handled Escape, so keyboard users could tab into the
page behind them and lost their place on close. useModalDialog moves focus
into the dialog on open, keeps Tab/Shift+Tab inside it, closes on Escape
and returns focus to the opener. IllustrationRouteDialog had the same
Escape-only pattern and now uses the hook too.

Unit tests cover focus entry, both wrap directions, recapturing escaped
focus and focus return (mutation-checked). Playwright keyboard tests for
both dialogs fail on the previous commit and pass now.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011d1MbSabs9nA9d9w8pxP1W
Resolve the docs/WORKLOG.md conflict by keeping both entries in time
order, and correct the accessibility entry's case count (6 skipped, plus
3 keyboard cases).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011d1MbSabs9nA9d9w8pxP1W
@anantham
anantham merged commit 9e79b03 into main Sep 29, 2026
8 checks passed

This branch was successfully deployed

1 active deployment
Preview — 6923ca0e Deployed Sep 26, 2026 by vercel[bot]
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