FocusScope's Tab interception defeats browser-native keyboard-focusable scroll containers, scrollable content in a Modal Dialog cannot be scrolled by keyboard (WCAG 2.1.1)
Los mantenedores suelen responder en 1 día
Nadie ha tomado este issue todavía.
Evaluación
- Dificultad
- 4/5
- Tiempo estimado
- 3-5 días
- Aptitud para principiantes
- 52/100
- Tipo de issue
- Error
- Claridad
- Bastante claro
- Estado de actividad
- Tranquilo
- Stack tecnológico
- react, typescript
- Área
- accessibility, frontend
Línea de trabajo
Comienza con el tabbable-tree walker de FocusScope y ejecuta la reproducción vinculada de StackBlitz en Chrome o Firefox. Compara el comportamiento de tabulación del cuerpo desplazable del diálogo con y sin tabIndex={0}; después, inspecciona el comportamiento relacionado de FocusScope señalado en las issues #6729 y #9570. Se considera terminado cuando los usuarios de teclado pueden acceder a un contenedor de diálogo desbordado y desplazarse por él de forma nativa sin requerir un tabIndex permanente innecesario.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
Provide a general summary of the issue here
Since Chrome 127 and Firefox, browsers automatically include scrollable containers without focusable children in the sequential tab order, so keyboard users can focus them and scroll with arrow keys (Chrome feature: keyboard-focusable scrollers). Inside a Dialog, FocusScope intercepts every Tab keypress (preventDefault) and moves focus itself using its own focusable-tree walker. That walker only recognizes conventionally focusable elements, so an implicitly keyboard-focusable scroll container is skipped entirely. As a result, a dialog with a scrollable content region (pinned header/footer with overflow: auto body, a very common dialog layout) is impossible to scroll with the keyboard: focus can never reach the scroller, and there is nothing arrow keys will scroll. This fails WCAG 2.1.1 (Keyboard) and axe's scrollable-region-focusable rule.
🤔 Expected Behavior?
Tab (as managed by FocusScope inside a Dialog) should be able to reach a scrollable container that the browser itself would place in the tab order, matching native behavior outside the focus trap. Once focused, arrow keys / PageUp / PageDown / Home / End scroll it natively.
😯 Current Behavior
Tab cycles only between conventionally focusable elements (buttons, inputs, [tabindex]). The scrollable region is unreachable; keyboard scrolling is impossible.
Reproduced by Claude Fable via iinstrumented reproduction in Chromium 149:
Without tabIndex on the scroller: Tab visits only the dialog's buttons (keydown for Tab arrives with defaultPrevented: true at the document, FocusScope moved focus itself). After ArrowDown ×2 + PageDown, the scroller's scrollTop is still 0.
With an explicit tabIndex={0} on the same element: Tab reaches it and native scrolling works immediately (ArrowDown → 40, PageDown → 550, End → bottom).
The same markup outside a FocusScope is keyboard-scrollable in Chrome 127+/Firefox with no tabindex, because native Tab handling includes the scroller.
💁 Possible Solution
Teach the focusable/tabbable tree walker used by FocusScope to treat an element as tabbable when the browser would: a scroll container (computed overflow scrollable and scrollHeight > clientHeight / scrollWidth > clientWidth) containing no other focusable elements, i.e. mirror Chrome's keyboard-focusable-scrollers heuristic. Alternatively (or until then), document that scrollable regions inside Dialog require an explicit tabIndex={0}.
Workaround we use today: hardcode tabIndex={0} on the dialog body. It works, but it adds a permanent tab stop even when the content doesn't overflow, which browsers' native heuristic avoids.
🔦 Context
We build a design-system Dialog on react-aria-components with a pinned header/footer and a scrolling body
.dialog {
display: flex;
flex-direction: column;
max-height: 80dvh;
overflow: hidden;
}
.dialog__body {
overflow-y: auto;
}
Our Storybook axe checks flag scrollable-region-focusable, and manual testing confirms keyboard users genuinely cannot scroll long dialog content (e.g. terms of service, multi-step forms). Comparable dialog implementations whose focus traps preserve native Tab handling via sentinel/guard elements get this right automatically in Chrome/Firefox — the regression is specific to focus traps that re-implement Tab navigation, which is what react-aria's FocusScope does. Related: #6729 (native video controls skipped by the same walker), #9570 (pointer interaction with scrollable content under contain).
🖥️ Steps to Reproduce
Created with the help of Claude AI.
https://stackblitz.com/edit/rac-dialog-scroll-issue?file=src%2FApp.tsx
- Open the dialog with the keyboard (Enter on the trigger).
- Press Tab repeatedly — focus only ever reaches the Close button; the scrollable is never focused.
- Press ArrowDown / PageDown — nothing scrolls (scrollTop stays 0).
- Control: render the same scrollable outside the Modal in Chrome 127+ — Tab focuses it natively and arrow keys scroll it.
- Control 2: add tabIndex={0} to the inside the Modal — Tab now reaches it and keyboard scrolling works.
Version
react-aria-components: 1.19.0 (react-aria 3.50.0)
What browsers are you seeing the problem on?
Chrome, Firefox
If other, please specify.
Browsers: Chromium 149.0.7827.55 (behavior applies to Chrome 127+ and Firefox, which ship keyboard-focusable scrollers; Safari lacks the native feature so it's equally broken there either way)
What operating system are you using?
Windows 11
🧢 Your Company/Team
No response
🕷 Tracking Issue
No response
- Lenguaje dominante
- TypeScript
- Estrellas
- 16k
- Forks
- 1.6k
- Merge medio
- 2 d 19 h
- PR fusionados (30 d)
- 41
Preparar el entorno
- Sin Dockerfile ni archivo de Docker Compose
- Tiene una plantilla de pull request
- Leer la guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de adobe/react-spectrum
-
Dificultad 2/5 Menos de una hora Aptitud para principiantes 88/100
adobe/react-spectrum#10909 ·
Los mantenedores suelen responder en 1 día
-
DatePicker omits timezone for initially empty fields despite zoned placeholderValuePosiblemente ocupada Un pull request vinculado a esta issue está abierto o ya se fusionó. Abiertobug
Dificultad 2/5 1-3 horas Aptitud para principiantes 68/100
adobe/react-spectrum#10900 · 2 comentarios ·
Los mantenedores suelen responder en 1 día
-
Separator adds a redundant role="separator" to its default <hr>Posiblemente ocupada @Janani-bn la tomó hoy. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 74/100
adobe/react-spectrum#10894 · 2 comentarios ·
Los mantenedores suelen responder en 1 día
-
ComboBox with selectionMode="multiple": Cmd/Ctrl+A does nothing while the popover is openPosiblemente ocupada @mangeshraut712 la tomó hace 6 días. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 76/100
adobe/react-spectrum#10866 ·
Los mantenedores suelen responder en 1 día
-
CheckboxGroup: `validationBehavior="aria"` is ignored by its checkboxes since 1.18Posiblemente ocupada @mangeshraut712 la tomó hace 7 días. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
adobe/react-spectrum#10856 ·
Los mantenedores suelen responder en 1 día
Todos los issues de adobe/react-spectrum
Issues similares
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
Los mantenedores suelen responder en 1 día
-
Dificultad 1/5 Menos de una hora Aptitud para principiantes 82/100
unocss/unocss#5421 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
needs-area
Dificultad 2/5 1-3 horas Aptitud para principiantes 68/100
Azure/typespec-azure#5936 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 Medio día Aptitud para principiantes 72/100
vercel-labs/scriptc#2910 ·
Los mantenedores suelen responder en 1 día
-
automation missing-model model-sync provider:fireworks-ai
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
anomalyco/models.dev#12458 ·
Los mantenedores suelen responder en 1 día