Skip to content

fix(a11y): sechs Bestands-Verstoesse beheben, Ausnahmen ersatzlos entfernen (#60) - #72

Merged
pseidler89-sudo merged 1 commit into
mainfrom
fix/60-a11y-bestandsfehler
Jul 26, 2026
Merged

fix(a11y): sechs Bestands-Verstoesse beheben, Ausnahmen ersatzlos entfernen (#60)#72
pseidler89-sudo merged 1 commit into
mainfrom
fix/60-a11y-bestandsfehler

Conversation

@pseidler89-sudo

Copy link
Copy Markdown
Owner

Folge-PR zu #71. Der dort gebaute a11y-Job hat sechs belegte Verstöße gefunden und als benannte Ausnahmen eingefroren. Dieser PR behebt sie wirklich und nimmt die Ausnahmen aus beiden Configs ersatzlos heraus.

Die Fixes

1 + 2 · duplicate-id-aria (critical, 2x) und form-field-multiple-labels (moderate, 2x) auf /

RegionEinstieg steht auf der Startseite zweimal (Hero + CTA-Wiederholung) und vergab die IDs statisch (plz, plz-funktion, plz-fehler, plz-hinweis) — jede ID existierte doppelt.

Real spürbar war das an der Label-Zuordnung: <label for="plz"> zeigt immer auf das erste Feld im Dokument. Ein Klick auf das Label des unteren Formulars sprang nach oben, Screenreader lasen die Beschreibung des falschen Feldes vor.

Fix: useId() statt fester Suffixe — ein Suffix trägt nur so lange, wie die Zahl der Instanzen bekannt ist. htmlFor, aria-describedby sowie die Fehler- und Hinweisknoten ziehen mit. name="plz" bleibt (Formularfeld-Name, keine ID).

3 · heading-order (moderate, 1x) auf /umfragen

In der anonymen Sicht folgten die Gruppentitel (Ortsteil/Kommune/Kreis/Land) als h3 direkt auf die Seiten-h1 — Ebenensprung. GruppierteListe bekommt eine Prop ebene (Default 3, die eingeloggte Sicht bleibt unter ihrer Sektions-h2 unverändert); die anonyme Sicht rendert h2. Es ändert sich nur die Ebene — die Klassen und damit die Optik sind identisch.

4 · link-in-text-block (serious, 1x) auf /anliegen

Der Link im Fließtext unterschied sich nur farblich vom umgebenden Text; hover:underline greift erst beim Hover — wer nicht mit der Maus darüberfährt (Touch, Tastatur, Restsehen), sieht es nie.

Das Muster steckt an ~70 Stellen im Repo, deshalb nicht punktuell, sondern eine Basis-Regel in globals.css: jeder <a> in einem <p> ist dauerhaft unterstrichen (ausgenommen .pz-btn — die tragen Fläche und Rahmen als Nicht-Farb-Merkmal). So schleppt auch der nächste neu geschriebene Fließtext-Link den Verstoß nicht wieder ein. Geprüft: es gibt aktuell keinen button-artigen <a> innerhalb eines <p>, die Regel trifft also nur echten Fließtext.

Ausnahmen entfernt

Aus .pa11yci.js und .pa11yci.streng.js sind duplicate-id-aria, form-field-multiple-labels, heading-order und link-in-text-block samt ihrer Kommentarblöcke raus.

Geblieben: die color-contrast-Ausnahme auf / in .pa11yci.streng.js. Sie ist keine Altlast, sondern die dokumentierte Grenze bei Verlaufshintergründen (axe kann dort prinzipiell nicht rechnen). An der Gate-Mechanik selbst ändert sich nichts.

Verifikation

Lokal gegen npm run start:standalone mit PILOT_TENANT_SLUG=taunusstein, frische DB (migrate + seed:regions + seed):

npm run a11yExit 0, beide Läufe ohne die entfernten Ausnahmen:

a11y-preflight bestanden — pa11y prüft echte, gestylte Tenant-Seiten.
Running Pa11y on 4 URLs:
 > http://127.0.0.1:3000/ - 0 errors
 > http://127.0.0.1:3000/umfragen - 0 errors
 > http://127.0.0.1:3000/anliegen - 0 errors
 > http://127.0.0.1:3000/anmelden - 0 errors
✔ 4/4 URLs passed
Running Pa11y on 4 URLs:
 > http://127.0.0.1:3000/ - 0 errors
 > http://127.0.0.1:3000/umfragen - 0 errors
 > http://127.0.0.1:3000/anliegen - 0 errors
 > http://127.0.0.1:3000/anmelden - 0 errors
✔ 4/4 URLs passed

Zusätzlich am gerenderten DOM von Hand geprüft (Gate-Grün allein beweist nicht, dass die Zuordnung fachlich stimmt — eine ID kann eindeutig und trotzdem falsch verdrahtet sein):

  • Startseite: 10 ID-Attribute, 0 doppelte.
  • Formular 1: input#_R_24lfmbtb_-plz, label[for] identisch, label.control === input (Browser-eigene Auflösung), input.labels.length === 1.
  • Formular 2: input#_R_9lfmbtb_-plz, ebenso, input.labels.length === 1.
  • Label-Klick Formular 1 → Fokus in Formular 1; Label-Klick Formular 2 → Fokus in Formular 2 (genau der Schaden, den die doppelten IDs verursacht haben).
  • Fehlerpfad: unbekannte PLZ ins zweite Formular → dessen aria-describedby wächst auf …-plz-funktion …-plz-hinweis, beide Knoten liegen im zweiten Block; das erste Feld bleibt unberührt, weiter keine doppelten IDs.
  • /umfragen anonym: Überschriften h1 → h2 → h3 → h2 → h3 → h2 → h3, keine Ebenensprünge.
  • /anliegen: beide Fließtext-Links haben im Ruhezustand text-decoration-line: underline.

npm run lint → Exit 0 · npm run typecheck → Exit 0 · npx vitest run → Exit 0, 95 Dateien / 1168 Tests grün.

🤖 Generated with Claude Code

https://claude.ai/code/session_01BervJUSWK8ymMbcuBuu9d9

…fernen (#60)

Der a11y-CI-Job aus PR #71 hat auf den Kernseiten sechs belegte Verstoesse
gefunden und sie als benannte Ausnahmen eingefroren. Dieser PR behebt sie und
nimmt die Ausnahmen aus BEIDEN Configs wieder heraus.

1) duplicate-id-aria (critical, 2x) + form-field-multiple-labels (moderate, 2x)
   auf /: RegionEinstieg steht auf der Startseite zweimal (Hero + CTA-
   Wiederholung) und vergab die IDs statisch — jede ID existierte doppelt.
   Real spuerbar an der Label-Zuordnung: <label for="plz"> zeigt immer auf das
   ERSTE Feld, ein Klick auf das Label des unteren Formulars sprang nach oben,
   Screenreader lasen die falsche Beschreibung vor. Jetzt useId() statt fester
   Suffixe (skaliert auf beliebig viele Instanzen); htmlFor, aria-describedby
   und die Fehler-/Hinweisknoten ziehen mit.

2) heading-order (moderate, 1x) auf /umfragen: In der anonymen Sicht folgten
   die Gruppentitel (Ortsteil/Kommune/Kreis/Land) als h3 direkt auf die
   Seiten-h1 — Ebenensprung. GruppierteListe bekommt eine Prop `ebene`
   (Default 3, eingeloggte Sicht unveraendert unter ihrer Sektions-h2); die
   anonyme Sicht rendert h2. Nur die Ebene aendert sich, die Klassen und damit
   die Optik bleiben identisch.

3) link-in-text-block (serious, 1x) auf /anliegen: Der Link im Fliesstext war
   nur farblich vom Text unterschieden, `hover:underline` greift erst beim
   Hover. Statt punktuell: eine Basis-Regel in globals.css unterstreicht jeden
   <a> in einem <p> dauerhaft (ausser .pz-btn). So schleppt auch der naechste
   neu geschriebene Fliesstext-Link den Verstoss nicht wieder ein.

Ausnahmen entfernt: duplicate-id-aria, form-field-multiple-labels,
heading-order und link-in-text-block samt Kommentarbloecken aus .pa11yci.js und
.pa11yci.streng.js. Die color-contrast-Ausnahme auf / bleibt — sie ist keine
Altlast, sondern die dokumentierte Grenze bei Verlaufshintergruenden.

Verifiziert: beide pa11y-Laeufe gruen ohne die Ausnahmen (4/4 URLs, 0 errors),
Label-Zuordnung am gerenderten DOM geprueft (IDs eindeutig, label.control zeigt
je auf das Feld im eigenen Formular, Label-Klick fokussiert das richtige Feld,
Hinweisknoten haengt am ausloesenden Formular).

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BervJUSWK8ymMbcuBuu9d9
@pseidler89-sudo
pseidler89-sudo merged commit 931c771 into main Jul 26, 2026
6 checks passed
@pseidler89-sudo
pseidler89-sudo deleted the fix/60-a11y-bestandsfehler branch July 26, 2026 13:00
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