Adjust order for keyboard
On Android, you can use several focus properties to modify the keyboard focus order.
android:nextFocusForward: set the next element to move focus to.android:nextFocusUp: specify which element should receive focus when navigating upandroid:nextFocusDown: specify which element should receive focus when navigating downandroid:nextFocusLeft: specify which element should receive focus when navigating to the leftandroid:nextFocusRight: specify which element should receive focus when navigating to the right
xml
<View
android:id="@+id/notFocusable"
android:focusable="false"/>
<EditText
android:id="@+id/field1"
android:focusable="true"
android:nextFocusForward="@+id/field2"
android:nextFocusDown="@+id/field3"
android:nextFocusRight="@+id/field2"/>
<EditText
android:id="@+id/field2"
android:focusable="true"
android:nextFocusForward="@+id/field3"
android:nextFocusDown="@+id/field4"/>
<EditText
android:id="@+id/field3"
android:focusable="true"
android:nextFocusForward="@+id/field4"/>
<EditText
android:id="@+id/field4"
android:focusable="true"/> In Jetpack Compose, to change the default focus traversal order for navigation, you can use the focusProperties.focusProperties(kotlin.Function1)) modifier to specify the item that should receive focus when navigating up, down, or in any other direction.
You can use the following focusProperties.focusProperties(kotlin.Function1)):
next): specifies which element should receive focus when navigating to the next.previous): specifies which element should receive focus when navigating to the previous.up): specifies which element should receive focus when navigating up.down): specifies which element should receive focus when navigating down.left): specifies which element should receive focus when navigating to the left.right): specifies which element should receive focus when navigating to the right.start): specifies which element should receive focus when navigating to the left in LTR mode and right in RTL mode.end): specifies which element should receive focus when navigating to the right in LTR mode and left in RTL mode.
kotlin
// Create set of reference for each Composable
val (first, second, third, fourth) = remember { FocusRequester.createRefs() }
Button(
onClick = { },
modifier = Modifier
.focusRequester(fourth)
.focusProperties {
down = third
right = second
}
) {
// Button content...
} On iOS, you can use the accessibilityRespondsToUserInteraction attribute to optimize keyboard navigation. By setting the property to false, the element will be skipped with keyboard navigation. Other assistive technologies, such as VoiceOver can still focus on the element. This way you can provide screen reader users with alternative text for images, but skip focus for keyboard users. When a hardware keyboard is connected and VoiceOver is enabled, the image will be focusable.
For even more concise control over the keyboard order, you can use properties such as canBecomeFocused,focusGroupIdentifier and focusGroupPriority. To debug focus, you can use UIFocusDebugger.
A use case could be a grid where you want to navigate by rows. You can achieve this by setting the same focusGroupIdentifier for each column in a row.
swift
grid.topLeft.focusGroupIdentifier = "top"
grid.topRight.focusGroupIdentifier = "top"
grid.bottomLeft.focusGroupIdentifier = "bottom"
grid.bottomRight.focusGroupIdentifier = "bottom" In SwiftUI, you can use the accessibilityRespondsToUserInteraction) view modifier to optimize keyboard navigation. By setting the property to false, the element will be skipped with keyboard navigation. Other assistive technologies, such as VoiceOver can still focus on the element. This way you can provide screen reader users with alternative text for images, but skip focus for keyboard users. When a hardware keyboard is connected and VoiceOver is enabled, the image will be focusable.
For more precise control over the keyboard order, you can use accessibilitySortPriority) view modifier to specify the priority of the item when using Full Keyboard Access.
swift
VStack {
HStack {
Text("Top Left")
// Focused first
.accessibilitySortPriority(4)
// Allow interaction using Full Keyboard Access
.accessibilityRespondsToUserInteraction(true)
Text("Top Right")
// Focused third
.accessibilitySortPriority(2)
.accessibilityRespondsToUserInteraction(true)
}
HStack {
Text("Bottom Left")
// Focused second
.accessibilitySortPriority(3)
.accessibilityRespondsToUserInteraction(true)
Text("Bottom Right")
// Not focusable by Full Keyboard Access
.accessibilityRespondsToUserInteraction(false)
}
} In Flutter, you can use FocusTraversalGroup to group widgets together. All subwidgets must be fully traversed before the keyboard focus is moved to the next widget. When grouping widgets into related groups is not enough, a FocusTraversalPolicy can be set to determine the ordering within the group.
The default ReadingOrderTraversalPolicy is usually sufficient, but in cases where more control over ordering is needed, an OrderedTraversalPolicy can be used. The order argument of the FocusTraversalOrder widget wrapped around the focusable components determines the order. The order can be any subclass of FocusOrder, but NumericFocusOrder and LexicalFocusOrder are provided.
Read more about Flutter's keyboard focus system.
:::warning
Full Keyboard Access (FKA) on iOS is not yet fully supported on Flutter.
- February 21, 2021: Flutter issue regarding FKA has been created
- October 24, 2024: Bare-bones FKA implementation has been merged
:::
The example below shows how to use the FocusTraversalOrder widget to traverse a row of buttons in the order TWO, ONE, THREE using NumericFocusOrder.
dart
FocusTraversalGroup(
policy: OrderedTraversalPolicy(),
Row(
children: <Widget>[
FocusTraversalOrder(
order: NumericFocusOrder(2.0),
child: TextButton(
child: const Text('ONE'),
),
),
const Spacer(),
FocusTraversalOrder(
order: NumericFocusOrder(1.0),
child: TextButton(
child: const Text('TWO'),
),
),
const Spacer(),
FocusTraversalOrder(
order: NumericFocusOrder(3.0),
child: TextButton(
child: const Text('THREE'),
),
),
],
),
); React Native has implemented all Android keyboard focus properties.
nextFocusForward: specify the next element to move focus tonextFocusUp: specify which element should receive focus when navigating upnextFocusDown: specify which element should receive focus when navigating downnextFocusLeft: specify which element should receive focus when navigating leftnextFocusRight: specify which element should receive focus when navigating right
It seems that none of the iOS keyboard focus properties have been implemented by React Native.
jsx
Not available, contribute! In MAUI, there is no built-in way to set the order, but you can use the SemanticOrderView from the MAUI Community Toolkit.
XAML Config
xml
xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
<toolkit:SemanticOrderView x:Name="SemanticOrderView">
<VerticalStackLayout>
<Entry x:Name="EmailEntry" />
<Entry x:Name="PasswordEntry" IsPassword="True" />
</VerticalStackLayout>
</toolkit:SemanticOrderView> csharp
SemanticOrderView.ViewOrder = new List<View> { EmailEntry, PasswordEntry }; Xamarin Forms supports changing the keyboard order through the TabIndex property. The default value is 0. The lower the value, the higher the priority.
The IsTabStop property can be used to exclude elements from tabbed navigation.
Read more about Keyboard Accessibility in Xamarin.Forms.
The code example below shows how exclude the label from receiving keyboard focus, and how to reach the save button before reaching the cancel button.
xml
<Label Text="Appt" IsTabStop="True" />
<Button x:Name="cancelButton" TabIndex="20" />
<Button x:Name="saveButton" TabIndex="10"/> Adjust order for assistive technologies
On Android, you can set the accessibility order in XML, or modify the accessibility order in code. You can use the android:accessibilityTraversalAfter and and android:accessibilityTraversalBefore properties in XML. Or you can use the setAccessibilityTraversalBefore) and setAccessibilityTraversalAfter) methods in code.
xml
<TextView
android:id="@+id/header" />
<RecyclerView
android:id="@+id/list"
android:accessibilityTraversalAfter="@id/description" />
<TextView
android:id="@+id/description"
android:accessibilityTraversalBefore="@id/header" /> kotlin
header.setAccessibilityTraversalBefore(R.id.description)
list.setAccessibilityTraversalAfter(R.id.description) In Jetpack Compose, you can use the traversalIndex.traversalIndex()) to alter the focus order of the screen. The traversalIndex will give assistive technologies an explicit order of traversing.
When a section of a screen is read out in an incorrect order, start by adding isTraversalGroup.isTraversalGroup()) to the parent Column, Row or Box. This will let assistive technologies know that this section is grouped and should be traversed, before moving on to a next section. Then add traversalIndex to elements in this group to fix any issues with the focus order.
It is possible to use isTraversalGroup and traversalIndex on the same element.
kotlin
Box(modifier = Modifier.semantics {
isTraversalGroup = true
traversalIndex = -1f
}) {
// Box content...
} On iOS, you can use the accessibilityElements property to set the order for assistive technologies. Be careful using the accessibilityElements property, because any elements left out of the array cannot be reached with assistive technologies.
swift
view.accessibilityElements = [header, description, list] In SwiftUI, assistive technology like VoiceOver typically reads elements in a top-left to bottom-right order. However, you can customize this reading order using the accessibilitySortPriority) view modifier. A higher priority value means the element is read earlier. Use .accessibilityElement(children: .contain) to group and manage the accessibility elements within stacks (HStack, VStack, or ZStack) to improve navigation when using assitive technologies.
swift
VStack {
Text("First Element")
.accessibilitySortPriority(2) // Reads second
Text("Second Element")
.accessibilitySortPriority(3) // Reads first
Text("Third Element")
.accessibilitySortPriority(1) // Reads third
}
.accessibilityElement(children: .contain) // Groups the stack's children For sorting the focus order in Flutter apps, the sortKey parameter of Semantics is used.
This parameter uses a SemanticsSortKey to sort the elements. The most common way to sort elements is the OrdinalSortKey, but you can also write your own implementation based on the SemanticsSortKey class.
The OrdinalSortKey needs an order as double and optionally a name as String. The elements are then sorted by name, with empty names handled first, and subsequently by order.
It is also possible to leave out the sortKey. In this case, Flutter will generate OrdinalSortKey's based on a platform specific algorithm. This order is often the order you want, but make sure to verify the sequence by using an assistive technology such as the screen reader.
dart
Widget focusOrderWidget(context) {
return Scaffold(
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Semantics(
sortKey: OrdinalSortKey(2.0),
child: Text("Second focus")
),
Semantics(
sortKey: OrdinalSortKey(1.0),
child: Text("First focus")
)
],
)
)
);
} React Native does not have support for changing the focus order. You can use the accessible prop to indicate that a view should be focusable. The child elements get grouped together.
More information about the lack of support for changing accessibility order can be found inside Discussion 389 of the React Native Community.
jsx
Not available, contribute! In MAUI, you can use a SemanticOrderView to control the order of VisualElements for screen readers. This can be particularly useful when building user interfaces in orders differing from the order in which users and screen readers will navigate them.
For more information, check out the SemanticOrderView documentation.
xml
<ContentPage
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
x:Class="CommunityToolkit.Maui.Sample.Pages.Views.SemanticOrderViewPage"
Title="Semantic Order View">
<ContentPage.Content>
<toolkit:SemanticOrderView x:Name="SemanticOrderView">
<Grid RowDefinitions="*,2*,*">
<Label Grid.Row="0" x:Name="DescriptionLabel" Text="Label for description, first label in xaml file" />
<Label Grid.Row="1" x:Name="TitleLabel" Text="Title, second label in xaml file" FontSize="30" />
<Label Grid.Row="2" Text="This label is excluded in the accessibility tree on iOS" />
</Grid>
</toolkit:SemanticOrderView>
</ContentPage.Content>
</ContentPage> csharp
using System.Collections.Generic;
namespace CommunityToolkit.Maui.Sample.Pages.Views;
public partial class SemanticOrderViewPage : ContentPage
{
public SemanticOrderViewPage()
{
InitializeComponent();
this.SemanticOrderView.ViewOrder = new List<View> { TitleLabel, DescriptionLabel };
}
} Xamarin Forms supports changing the accessibility order through the TabIndex property. The default value is 0. The lower the value, the higher the priority. For example, to reach the save button before reaching the cancel button, the cancel button's TabIndex needs to be higher than the save button.
xml
<Label Text="Appt" />
<Button x:Name="cancelButton" TabIndex="20" />
<Button x:Name="saveButton" TabIndex="10"/> Move accessibility focus
On Android, you can send an AccessibilityEvent of the type TYPE_VIEW_FOCUSED to move the focus of assistive technologies to a specific view. The view must be focusable for this event to take effect.
kotlin
fun focus(view: View) {
view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED)
} In Jetpack Compose, to request focus for a Composable, you need to attach the focusRequester, and then call it from the desired place. The Composable must be focusable for this to take effect.
kotlin
// Request focus on Composition start
val focusRequester = remember { FocusRequester() }
TextField(
// ... textField setup
modifier = Modifier.focusRequester(focusRequester)
)
LaunchedEffect(Unit) {
focusRequester.requestFocus()
} On iOS, you can use UIAccessibility to post a notification to move the focus of assistive technologies. Use screenChanged when a new view appears that occupies a major portion of the screen. Otherwise, use layoutChanged when the layout of current screen changes.
swift
func focus(_ view: UIView) {
UIAccessibility.post(notification: .layoutChanged, argument: view)
UIAccessibility.post(notification: .screenChanged, argument: view)
} In SwiftUI, you can enhance accessibility focus by using the @AccessibilityFocusState property wrapper and the accessibilityFocused) modifier. These methods allow you to programmatically move the accessibility focus to a specific element in your app. This can be particularly useful when you want to direct the user's attention to a specific part of the UI in response to changes or interactions.
swift
// State variable to control loading state
@State var isLoading: Bool = false
// Accessibility focus state variable to manage focus
@AccessibilityFocusState var isLoadingIndicatorFocused: Bool
var body: some View {
VStack {
Button("Search Appt website") {
// Set loading state to true
isLoading = true
// Move focus to the loading indicator
isLoadingIndicatorFocused = true
}
if isLoading {
ProgressView()
.accessibilityFocused($isLoadingIndicatorFocused) // Bind the focus state
.accessibilityLabel("Loading") // Provide an accessible label
}
}
} In Flutter, you can use a FocusSemanticEvent to move the accessibility focus.
This API is generally not recommended because it can disrupt users' expectations of accessibility focus.
It should be used carefully and only in specific cases, like replacing a focused rendering object with another, though such designs should generally be avoided.
Note: do not use FocusNode or Semantics.focused, these methods should only be used for keyboard or input focus.
dart
class ApptWidget extends StatelessWidget {
final GlobalKey _key = GlobalKey();
@override
Widget build(BuildContext context) {
// Ensure focus change occurs after rendering.
WidgetsBinding.instance.addPostFrameCallback((_) {
_key.currentContext
?.findRenderObject()
?.sendSemanticsEvent(const FocusSemanticEvent());
});
return Text('FocusSemanticEvent', key: _key);
}
} In React Native you can move accessibility focus by using the setAccessibilityFocus method from the AccessibilityInfo class. This method requires a reactTag, which you can find by calling the findNodeHandle method.
tsx
function Component() {
const ref = useRef(null);
function setFocus() {
const reactTag = findNodeHandle(ref.current);
if (reactTag) {
AccessibilityInfo.setAccessibilityFocus(reactTag);
}
}
return <View ref={ref} accessible accessibilityLabel="Modal" />
}; In MAUI, the SemanticExtensions class contains the SetSemanticFocus method. This method moves the accessibility focus to the given element on the native platform.
The code sample below shows how to move the accessibility focus to a specific element.
xml
<HorizontalStackLayout
x:Name="MenuButtonLayout"
AutomationId="MenuButton"
AutomationProperties.IsInAccessibleTree="True"
SemanticProperties.Description="Menu container"
Padding="0,0,5,0">
<Button
Text="Click to set semantic focus to the label below"
Clicked="SetSemanticFocus_Clicked"/>
<Label
x:Name="semanticFocusLbl"
Text="Label to set semantic focus"/>
</HorizontalStackLayout> csharp
private void SetSemanticFocus_Clicked(object sender, System.EventArgs e)
{
semanticFocusLbl.SetSemanticFocus();
} Xamarin Forms does not have built-in support for changing accessibility focus.
The SemanticExtensions file inside the Xamarin.CommunityToolkit contains the SetSemanticFocus method. It moves the accessibility focus to the given element on the native platform.
csharp
SemanticExtensions.SetSemanticFocus(element)