[Regression on closed #366] all six route-level loading.tsx skeletons have lost their ARIA live region — route changes are silent for screen readers
I maintainer di solito rispondono entro 1 giorno
Nessuno ha ancora preso questa issue.
Valutazione
- Difficoltà
- 3/5
- Tempo stimato
- 1-2 giorni
- Idoneità per principianti
- 72/100
- Tipo di issue
- Bug
- Chiarezza
- Specificata chiaramente
- Stato di attività
- Attiva
- Stack tecnologico
- next.js, react, tailwindcss, typescript
- Ambito
- accessibility, frontend
Direzione di ricerca
Read the six route boundaries at src/app/loading.tsx, src/app/dashboard/loading.tsx, src/app/issues/loading.tsx, src/app/issues/[id]/loading.tsx, src/app/milestones/loading.tsx, and src/app/reputation/[handle]/loading.tsx. Compare their accessibility pattern with src/app/auth/callback/page.tsx and check the loading-boundary test file mentioned for #667. Done when each boundary announces loading accessibly, supports reduced motion, and has test coverage for role="status".
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
Location
All six route-level loading boundaries, none of which carries a live region:
src/app/loading.tsx:5src/app/dashboard/loading.tsx:5src/app/issues/loading.tsx:3src/app/issues/[id]/loading.tsxsrc/app/milestones/loading.tsxsrc/app/reputation/[handle]/loading.tsx
Problem
Each file returns a purely visual skeleton — a container with animate-pulse and a grid of grey placeholder <div>s — and nothing else:
export default function Loading() {
return (
<div className="mx-auto max-w-6xl px-6 py-12 animate-pulse">
<div className="h-8 w-48 rounded-lg bg-slate-200 dark:bg-slate-800" />
...
grep -rn 'role=\|aria-' src/app/**/loading.tsx returns zero matches across all six files. There is no role="status", no aria-live, no aria-busy, and no visually-hidden text naming what is loading. Nothing higher in the tree supplies one either: src/app/layout.tsx wraps {children} in a bare <main id="main-content"> with no live region, and Next.js renders a loading.tsx as the Suspense fallback in that same slot.
This is precisely what closed issue #366 was about — "None of the five route-level loading.tsx skeleton screens have an ARIA live-region/status role" — and the fix is no longer present in any of the files, including the sixth one added since. For contrast, the pattern is still correct elsewhere in the app: src/app/auth/callback/page.tsx:15 and src/app/auth/callback/CallbackClient.tsx:154 both use <p role="status" aria-live="polite">, so the convention exists and these six files diverge from it.
Why it matters
Client-side navigation in the App Router does not reload the document, so a screen reader gets no page-load announcement. The skeleton is the only indication that anything is happening, and it is announced as nothing at all — a sighted user sees a shimmer, a screen-reader user hears silence between activating a link and the new page's content appearing. On /issues/[id] and /reputation/[handle], where the fetch can take a full second against a live backend, the user has no way to tell whether their activation registered, and is likely to activate the link again.
animate-pulse also runs unconditionally, so the skeleton ignores prefers-reduced-motion — a smaller instance of what closed issue #258 covered app-wide.
Reproduction
npm run dev, then enable VoiceOver (macOS) or NVDA (Windows).- From
/issues, activate a bounty card link. - The skeleton from
src/app/issues/[id]/loading.tsxrenders. Nothing is announced; the screen reader stays silent until the resolved page content lands and focus happens to be moved there. curl -s http://localhost:3000/issues | grep -c 'role="status"'against a slow backend returns 0 for the skeleton markup.
Suggested fix
Give each skeleton a polite live region with an accessible name, and mark the region busy while it is up. For example, in each loading.tsx:
<div
role="status"
aria-live="polite"
aria-busy="true"
className="mx-auto max-w-6xl px-6 py-12 animate-pulse motion-reduce:animate-none"
>
<span className="sr-only">Loading bounties…</span>
{/* existing placeholder divs, with aria-hidden="true" on the wrapper that holds them */}
Because all six files repeat the same structure, the cleanest version is a single shared <RouteSkeleton label="…"> component in src/components/ui/ that owns the live region, the sr-only label and the motion-reduce:animate-none class, with each loading.tsx passing only its own label and placeholder shape. That also stops the next new route from reintroducing the gap.
Add an assertion to the loading-boundary test file (added for closed #667) that every loading.tsx default export renders an element with role="status", so this cannot silently regress a third time.
- Lingua principale
- TypeScript
- Stelle
- 1
- Fork
- 94
- Merge medio
- 1h 2m
- PR unite (30g)
- 25
Preparare l'ambiente
- Nessun Dockerfile né file Docker Compose
- Ha un modello di pull request
- Leggi la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di MergeFi/frontend
-
bug
Difficoltà 2/5 1-3 ore Idoneità per principianti 82/100
I maintainer di solito rispondono entro 1 giorno
-
bug testing
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
I maintainer di solito rispondono entro 1 giorno
-
bug
Difficoltà 2/5 1-3 ore Idoneità per principianti 76/100
I maintainer di solito rispondono entro 1 giorno
-
bug
Difficoltà 2/5 1-3 ore Idoneità per principianti 84/100
I maintainer di solito rispondono entro 1 giorno
-
accessibility bug
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
MergeFi/frontend#835 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
Tutte le issue di MergeFi/frontend
Issue simili
-
ログイン中の404画面にヘッダーとナビを出すApertaCodex使用可 コード
Difficoltà 2/5 1-3 ore Idoneità per principianti 72/100
ryuki-moro/Nicomaru#218 ·
I maintainer di solito rispondono entro 3 giorni
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 72/100
sveltejs/svelte-eslint-parser#1008 ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
silverbucket/inbox-rs#351 ·
I maintainer di solito rispondono entro 1 giorno
-
[Bug]: The H2 series' and the H3C's fan 3 is labelled "Chamber Fan", Bambu calls it the exhaust fanApertaarea:ui
Difficoltà 2/5 1-3 ore Idoneità per principianti 88/100
I maintainer di solito rispondono entro 1 giorno
-
bug front P2
Difficoltà 2/5 1-3 ore Idoneità per principianti 86/100
invoicerr-app/invoicerr#964 ·
I maintainer di solito rispondono entro 1 giorno