Skip to content

feat(a11y): text reaches WCAG AA on every dark page — and the tools speak - #301

Merged
MrChengLen merged 1 commit into
mainfrom
pr-a11y-contrast
Oct 29, 2026
Merged

MrChengLen merged 1 commit into
mainfrom
pr-a11y-contrast

Conversation

@MrChengLen

Copy link
Copy Markdown
Owner

What

WCAG 2.2 AA on the dark pages, through the design tokens:

  • Contrast. ink.faint #6b7280 → #8b929e (≥ 4.69 : 1 on every dark surface up to gray-800); raw text-gray-500/600 → ink tokens in all templates and JS; brand-coloured text → brand-light; filled buttons bg-brand-dark (hover new brand-deep); download buttons emerald-700/800; disabled buttons ink-dim. Logo, icons and the focus ring keep brand.
  • Screen readers. One always-present polite live region #fm-live + announce.js: the converter, PDF tools and redaction tool announce progress and the result card as shown; error texts are role="alert"; error paths empty the region first.
  • Keyboard & names. Skip link "Skip to main content" as the first Tab stop (not on /cancel), focus ring on raw fields, quality slider and target-size field named by their headings, compress-mode buttons with aria-pressed and a visible highlight, account button with a name, aria-expanded, open on focus, Escape to close.
  • Phones. 16 px text in every form field (iOS no longer zooms in); larger tap targets in the mobile account menu.
  • Found in review, fixed because the skip link would trigger them: Enter submitted the sign-in/registration/password forms from anywhere on the page (now only from a field); the password-reset page showed "email sent" regardless of the response; the /formats search lost its #q= on the skip link's hash; the batch ZIP label counted selected instead of succeeded files.

Measured

Live scan of every text element against its real background (local build): 0 elements below AA on /de/ (119), /de/tools (123), /de/pdf/insert (83), /en/compress (78), /de/formats (310), /de/contact (48). Before, on production: 52 of 128 on the home page and 58 of 133 on /tools.

Tests

  • Guards in tests/test_public_claims.py §13: every text token against all dark surfaces from tailwind.config.js; no low-contrast greys, no brand-coloured text, no white-on-brand/emerald-600 fills in templates or JS (Jinja-glued classes included).
  • New tests/test_accessibility.py (skip link DE/EN, live region + script, alerts, announcements, input names) and a skip-link test for /cancel.
  • Full suite 3082 passed, 271 skipped (Windows); ruff + format clean; i18n drift-check clean (one new msgid, DE/EN); Tailwind rebuilt.
  • Reviewed by security-auditor (READY) and code-reviewer (READY after the fixes above).

🤖 Generated with Claude Code

…peak

About 40 % of the text on the home and /tools pages fell below 4.5 : 1:
raw text-gray-500 (4.16), text-gray-600 (2.3–2.7), indigo text on
cards (3.9), white on the indigo buttons (4.47) and on the green
download button (3.8, hover 2.5). The fix runs through the design
tokens, so every page follows from one place:

- ink.faint #6b7280 → #8b929e (≥ 4.69 : 1 even on gray-800), and the
  raw greys in templates and JS now use it; decorative upload icons
  and disabled buttons take ink-dim (disabled still looks disabled).
- Brand-coloured text uses brand-light; filled buttons use brand-dark
  with a new brand-deep hover (white on them 6.3 / 7.9 : 1). The logo,
  icons and focus ring keep brand. Download buttons emerald-700/800.
- Raw inputs and selects get the components' focus ring instead of a
  1 px border change, and every form field uses 16 px text on phones,
  so iOS stops zooming in.

Screen readers: one always-present polite live region (#fm-live) and
announce.js. The converter, the PDF tools and the redaction tool
announce the progress text and the result card as shown (download
label, hints, batch summary); error texts are role="alert", and every
error path empties the region first. Only text already on screen is
read out. A skip link ("Skip to main content") is the first Tab stop
on every page except the § 312k /cancel page; the quality slider and
the target-size input are named by their headings; the compress-mode
buttons show and announce which one is selected (a grey background
covered the highlight); the account button has a name, aria-expanded,
opens on keyboard focus and closes with Escape; the mobile-menu entries
for the dashboard and sign-out get the same tap height as the rest.

Found in review and fixed here, because the skip link would trigger
them: the sign-in, registration and password pages submitted on Enter
anywhere on the page — now only from a field — and the password-reset
page showed "email sent" whatever the server answered; it now checks
the field and the response. The /formats search keeps its #q= when the
skip link changes the hash. The batch download label counts the files
in the ZIP (X-FileMorph-Batch-Succeeded), not the selected ones.

Guards: §13 of test_public_claims checks every text token against all
dark surfaces from tailwind.config.js and bans the low-contrast greys,
brand-coloured text and the white-on-brand/emerald-600 fills in
templates and JS; test_accessibility pins the skip link, the live
region, the alerts and the input names. The admin cockpit's colour
keys and the aria-hidden gray-700 separators stay as they are.
scripts/make_testdata_a11y_preview.py writes the photo for the manual
check.

Full suite 3082 green; ruff + i18n-drift clean; Tailwind rebuilt;
requirements untouched.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MrChengLen
MrChengLen merged commit 57c1232 into main Oct 29, 2026
6 checks passed
@MrChengLen
MrChengLen deleted the pr-a11y-contrast branch October 29, 2026 15:43
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.

1 participant