It's All About (Accessibility) Focus And Compose

Reading time: about 8 minutes

Published

It's All About (Accessibility) Focus And Compose
  1. Accessibility Focus
  2. Keyboard Focus
  3. Making a Component Focusable
  4. Moving Focus Programmatically to a Component
  5. Changing Traversal Order
  6. Wrapping Up
  7. Links in the Blog Post

I've seen multiple questions in various Slack communities, Stack Overflow, and other places related to focus on Android and how it doesn't behave as expected. The question typically concerns the use of focusRequester and then inquires why Talkback or other assistive technologies fail to set focus correctly.

The simple answer is that these APIs are different, and focus behaves differently with accessibility focus and keyboard focus.

In this blog post, I'll discuss the differences and what you can do in different cases. Let's first look at what I mean when I talk about accessibility focus and keyboard focus.

Accessibility Focus

In this blog post, accessibility focus refers to the focus related to screen readers, such as TalkBack. It could include focus for switch access as well, but I'm leaving it out of the scope of this blog post.

When using a screen reader, accessibility focus can be set to any element relevant to the user, such as interactive elements, text, images with text alternatives (content descriptions), and other meaningful elements. The focus indicator can look different on different phones, but here's an example from my phone:

In the picture, there are two instances of the same UI element side by side: first, a button with the text 'Button', and below it, a switch with the label 'Switch'. Under the first one, there's a text Button focused, and there is a green rectangle around the button. Under the second one, the text is Switch focused, and the green rectangle is around the Switch and its label.

It's set by the system, so the app developer can't edit it, and there shouldn't be any need to do that either.

Keyboard Focus

Keyboard focus, on the other hand, is the focus that interactive elements receive when a user uses a keyboard, D-pad, or other keyboard-emulating device for navigation.

Only interactive elements should be focusable; never, for example, headings or other text. The default focus indicator is the ripple, so it's not that visible:

In the picture, there are two instances of the same UI element side by side: first, a button with the text 'Button', and below it, a switch with the label 'Switch'. Under the first one, there's a text Button focused, and the button is almost in a non-noticeably lighter color. Under the second one, the text is Switch focused, and the background of the Switch and the label is almost in a non-noticeably lighter color.

Later, I will write a blog post about customising and making the focus indicator pass the accessibility legal requirements with color contrast and other requirements.

Making a Component Focusable

So you're creating a custom component, and wondering if it's the kind that should be focusable. The short and simple answer is that every component that the user can interact with via touch input should most likely be focusable. The actual interaction should be handled with accessibility actions and key events, depending on the component.

To put it even more clearly: every button, checkbox, editable text, swipeable element, and graph that has touch input for either/both scrolling or/and data point exploration, should usually be focusable.

However, note that when using Material components, such as buttons and other interactive elements, they are already focusable, so you don't need to add it manually. Adding, for example, a focusable modifier makes the element appear twice in the focus order.

There are several ways to make a custom interactive element focusable for both screen readers and keyboard navigation.

Using clickable, toggleable, or selectable modifiers. If you're creating a component that the user should be able to either click, toggle, or select, use the respective modifiers. They make the component focusable as well.

Using focusable-modifier. Sometimes a component needs to be focusable, but it's not clickable, toggleable, or selectable. It might, for example, require custom keyboard shortcuts, such as a chart with touch input that reveals more data with touch gestures. In these cases, using the focusable modifier is the right choice for making the component focusable.

Moving Focus Programmatically to a Component

The next thing we need to discuss is moving the focus programmatically based on the user's actions. For keyboard and keyboard-emulating navigation methods, this is relatively straightforward. For screen readers, it requires a little bit more. Let's first discuss the keyboard focus and then the accessibility focus.

Keyboard Focus

If you want to move keyboard focus from an element when a new screen appears, or on, let's say, a button click, FocusRequester is your friend.

Here's what using it looks like in code:

@Composable
fun Component() {
  val focusRequester = remember { FocusRequester() }

  Button(
    onClick = { focusRequester.requestFocus() }
  ) { 
    Text("Move focus") 
  }

  CustomComponentWereFocusing(
    modifier = Modifier
      .focusRequester(focusRequester)
      .toggleable(...)
    )
}

In this example, when the user clicks the Button, focus moves to the CustomComponentWereFocusing. We first define the focusRequester by remembering FocusRequester(), then set it to the focusRequester modifier for the CustomComponentWereFocusing. Finally, we call FocusRequester's requestFocus() method on button click.

One important thing to remember here, as with other modifiers, is that the order of modifiers matters. So, when setting the focusRequester modifier, it must be placed before the modifier that adds focusability to the component. Otherwise, it doesn't work at all.

Accessibility Focus

Moving the accessibility focus for screen readers needs a little bit more work. The focusRequester doesn't work here, so we need to resort to workarounds to accomplish it. I'll share some ideas; the final implementation, naturally, depends on the actual use case.

The first suggestion is that, in some cases, changing the traversal order may be the solution. The following section discusses this topic, so keep reading for tips on how to do that.

Then there's a solution for changing the focused property on the semantics modifier. I found this solution from Kotlinlang Slack's Compose channel.

The example from the keyboard navigation section with the ability to focus with a screen reader would look like this:

@Composable
fun Component() {
  val isFocused by remember { mutableStateOf(false) }

  LaunchedEffect(isFocused) {
    if (isFocused) isFocused = false
  }

  Button(
    onClick = { isFocused = true }
  ) { 
    Text("Move focus") 
  }

  CustomComponentWereFocusing(
    modifier = Modifier
      .semantics { focused = isFocused }
      .toggleable(...)
  )
}

So, here we first define a boolean variable isFocused, which we remember. Then, we set the focused property of the semantics modifier to isFocused for CustomComponentWereFocusing. On the button click, we set the isFocused to true to move the focus to CustomComponentWereFocusing.

And finally, we have a LaunchedEffect listening for changes to isFocused, and if it's true, we set it back to false. It doesn't clear the focus from CustomComponentWereFocusing, but lets us refocus with a new button click if needed.

Here, again, the order of the modifiers matters - semantics needs to be before the focusability-applying modifier, which is toggleable in the example.

Changing Traversal Order

The final topic for this blog post is changing traversal order. Let's say we have a layout with two rows and two columns. The layout looks like this:

Four buttons on two rows, two on each row. The texts on the first row are First and Third, and on the second row Second and Fourth.

The default focus order would be as follows: first top row, then bottom row - meaning First, Third, Second, Fourth. And we want it to be in numerical order.

The solutions for accessibility and keyboard focus are different, so let's discuss them separately, starting with accessibility focus.

Accessibility Focus

For accessibility focus, using the semantics modifier, isTraversalGroup, and traversalIndex is the way to customise the focus order.

First, let's set the isTraversalGroup property to true for the parent component wrapping the two rows. Why? It's to create a border for our changes in traversal order. You see, if we don't set it, and set the traversalIndex to the buttons, they'd be last in the screen's focus order, because their traversal index is greater than 0. Zero is the default traversal index for elements that don't have the traversal index explicitly set. However, if we set the border with isTraversalGroup, then the changes will only affect the focus order within the traversal group, not the screen's focus order.

So, in code it would look like the following:

Column(
  modifier = Modifier
    .semantics {
      isTraversalGroup = true
    }
) {
    ...
}

And then we set the traversalIndex for each of the buttons to match the focus order we want to create:

Row(...) {
    Button(
        modifier = Modifier.semantics { 
            traversalIndex = 1f
        },
        ... 
    ) { Text("First") }
    Button(
        modifier = Modifier.semantics { 
            traversalIndex = 3f
        },
        ... 
    ) { Text("Third") }
 }

I've omitted the other row, which contains the "Second" and "Fourth" buttons, from the example, but the idea remains the same.

And that's it. With these changes, the focus order for accessibility focus changes. Let's talk about keyboard focus-related changes next.

Keyboard Focus

For keyboard focus, we first need to create FocusRequesters for each button with FocusRequester.createRefs(), and then attach them with focusRequester modifier, and finally, set the focus order with focusProperties modifier.

Creating the FocusRequesters looks like the following code:

val (first, second, third, fourth) = remember { 
  FocusRequester.createRefs() 
}

Next, we set the FocusRequesters with focusRequester modifier and the next element to focus on with focusProperties modifier:

Row(...) {
  Button(
    modifier = Modifier
      .focusRequester(first)
      .focusProperties { next = second},
    ... 
  ) { Text("First") }
  Button(
    modifier = Modifier
      .focusRequester(third)
      .focusProperties { next = fourth},
    ... 
  ) { Text("Third") }
}

I've omitted the second row from the example, but it would be similar.

This way, keyboard and keyboard-emulating device navigating users would first get to the "First" button, then "Second", then "Third", and finally, "Fourth".

Wrapping Up

In this blog post, I've discussed the differences between focusing with screen readers (e.g., TalkBack) and navigating with the keyboard. I've also demonstrated the differences between the two in changing the currently focused element and altering the traversal/focus order.

If you want to read the docs, Modify traversal order is the page to check for accessibility focus, and Focus in Compose summarizes the information about keyboard focus.

Links in the Blog Post