focus-trap
Mobile validation
A focus trap occurs when keyboard focus gets stuck cycling within a group of elements, and users cannot move on to other parts of the UI. Everything outside the loop becomes unreachable with the keyboard.
This often happens when a custom focus order is set with nextFocusForward or the other nextFocus attributes, and the chain leads back to an earlier element instead of continuing through the screen. In most cases, don't override the default focus order. If you do, make sure the chain reaches every interactive element and lets focus continue past the last one.
Note: This validation is about keyboard focus. Screen readers and switch access move through the screen in the accessibility order, which the nextFocus attributes do not change.
Focus traps on iOS and in Flutter
The examples below use Android's nextFocus attributes, Jetpack Compose's focusProperties, and React Native's Android-only nextFocusForward prop. Other platforms have no single attribute that causes this issue, but focus traps can still happen:
- UIKit and SwiftUI: iOS has no per-element "next focus" attribute. Keyboard focus is managed with focus groups and focus guides, and in SwiftUI also with focus sections and
@FocusState. A trap usually comes from custom focus logic, such as code that moves focus back whenever it leaves an area. - Flutter: Focus order is managed with
FocusTraversalGroup, traversal policies, andFocusScope. A misconfigured scope or custom traversal logic can keep focus inside part of the screen.
On these platforms, review any custom focus logic and test it with a keyboard, as described in Try It Yourself.
Pass Patterns
Android
XML Views
The buttons have no nextFocus attributes, so focus moves through all of them, including Continue, and then out of the group.
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button 1" />
<Button
android:id="@+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button 2" />
<Button
android:id="@+id/button3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button 3" />
<Button
android:id="@+id/continueButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Continue" />
</LinearLayout>Fail Patterns
Android
XML Views
The nextFocusForward attributes send focus from Button 1 to Button 3 and back to Button 1, so Button 2 and Continue can never receive focus.
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:nextFocusForward="@id/button3"
android:text="Button 1" />
<Button
android:id="@+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button 2" />
<Button
android:id="@+id/button3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:nextFocusForward="@id/button1"
android:text="Button 3" />
<Button
android:id="@+id/continueButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Continue" />
</LinearLayout>Try It Yourself
Using a keyboard
- Connect an external keyboard to the device. On iOS, also turn on Settings > Accessibility > Keyboards > Full Keyboard Access.
- Press Tab repeatedly to move focus through the screen. You may also need to use the arrow keys within a container.
- Check whether focus reaches every interactive element and can move past each group of elements.
Expected results: Focus moves through all the interactive elements on the screen and does not get stuck in a loop.
Actual results: Focus cycles within a group of elements, and some elements on the screen can never receive focus.
FAQ
How does this issue relate to 2.1.2 No Keyboard Trap?
SC 2.1.2 requires that if keyboard focus can move into a component, it can also move away from it using only the keyboard. When a custom focus order creates a loop, focus cannot leave the looped elements with standard keys, so the criterion is not met.
Why is this issue marked as Critical?
A focus trap makes the rest of the screen unreachable for keyboard users. They cannot get to other controls or content, which prevents them from completing tasks, so it is rated critical.
Standards and Regulations
| Standard | Success Criterion | Reference |
|---|---|---|
| WCAG 2.2 | 2.1.2 No Keyboard Trap (A) | If keyboard focus can be moved to a component using a keyboard interface, focus can be moved away from that component using only a keyboard interface, and if it requires more than unmodified arrow or tab keys, the user is advised of the method. |
| EN 301 549 | 11.2.1.2 No Keyboard Trap | Same as the WCAG text. |
Personas impacted by this issue
Learn More
- Understanding Success Criterion 2.1.2: No Keyboard Trap
- Android: Support keyboard navigation (Views)
- Android: Change focus traversal order in Jetpack Compose
- React Native: View props (nextFocusForward)
- Apple: Focus-based navigation (UIKit)
- Apple: Focus (SwiftUI)
- Flutter: Understanding focus
- Flutter: FocusTraversalGroup class