I can't figure out how I'd implement the "tab ente...
# compose-desktop
j
I can't figure out how I'd implement the "tab enters group, arrow keys move focus within group" behavior in my compose desktop application, while still allowing the children to be visible (and focusable) via accessibility tools. I'm talking about the focus behavior that e.g. the room list of Slack/Teams has, or that's typical e.g. in Win32, Qt or Cocoa apps. The samples tell me I'd have to manually wire up all the next/prev/left/right/up/down focus targets, but I can't really know which those are in an individual component. I could disable focus for all the list items, and only enable focus on the parent item, but then I'd also lose most of the accessibility interaction, right?
a
I don’t think there’s built-in focus movement on arrow keys. You need to implement that yourself.
In any case, an example of what you’re trying to achieve would help us help you.
j
an example of what you’re trying to achieve
Short: As mentioned, the same focus behavior as the room list in Slack or Teams.
Long: First, UI frameworks usually have a dropdown/select element. You select it with tab, then use arrow keys to navigate between the options.
Second, most UI frameworks also have the option to have the exact same functionality, behavior and accessibility, but as static/persistent part of the UI
Third, most chat clients from the very first IRC clients to modern Slack and Teams use UI elements with this very same behavior for their room list UI
As an example, the Slack room list that you've got on your monitor right this moment:
It's exposed in accessibility properties as a tree, and behaves like a select UI element in every way
Our requirements: 1. The entire room list is visible at all times to accessibility tools, and screenreaders see any room in the list as focusable at any time 2. For tab navigation, the room list behaves as one single item. Tab enters it, and pressing tab again leaves it. 3. Within of the room list, focus is moved with arrow keys. This is actually moving the focus, visible to screenreaders and other accessibility tools.
a
Are you complaining about the lack of a widget, or difficulty to implement one, or…?
j
What I've tried: 1. Setting focusProperties next/previous for each and every room list item, and the room list as a whole. But that requires that I know the 2D layout of the app itself ahead of time, as I need to know which elements exist after/before the room list 2. Setting all items except for one as not focusable, and then using hundreds of focus requesters to move the focus manually. But then the other items aren't visible as focusable to screen readers.
My current solution is creating a focus requester for each and every list item in the view model, and then keeping track of which is the active item as a MutableState<FocusRequester> Then I set focusProperties { canFocus } on all items, so only the active item can be focused (which means only one item is available for tab navigation), and when I want to shift focus, I update the active item, which leads to all items updating their canFocus state, followed by a LaunchedEffect using
requestFocus()
But this leads to focus flickering, and all other items not showing as focusable to screen readers.
Are you complaining about the lack of a widget, or difficulty to implement one, or…?
I'm asking because I'm assuming there's an easy and obvious way to do this that I've missed. I'm assuming I'm doing something wrong.
a
It would help if you showed a simplified snippet of Compose code and what you’re trying to achieve with it.
j
Do you understand what I'm trying to do? Is that part clear, at least?
The question can be shortened to this: 1. How do I configure a container so that Tab does NOT navigate within of the container, but all items of the container are nonetheless focusable for screenreaders, or d-pad focus movement? 2. How do I move focus within of a container not just Up/Down one element, but Up/Down a whole page, or to the beginning/end?
We need to comply with WCAG interaction standards in our compose desktop and web apps, in this case it's https://www.w3.org/WAI/ARIA/apg/patterns/listbox/#keyboardinteraction that we need to follow.
See also https://www.w3.org/WAI/ARIA/apg/practices/keyboard-interface/#introduction
A primary keyboard navigation convention common across all platforms is that the tab and shift + tab keys move focus from one UI component to another while other keys, primarily the arrow keys, move focus inside of components that include multiple focusable elements. The path that the focus follows when pressing the tab key is known as the tab sequence or tab ring.
If there's something like roving tabindex, independently of the focusable state, that'd also solve my problem.
a
It doesn’t sound like standard behavior to me, but I could be wrong. I think you’ll need to implement all the focus transitions manually, using
FocusManager
. Maybe via Up/Down directions. Also, you’ll probably get better help in the generic #CJLTWPH7S channel. I don’t think you’ll need any desktop-specific APIs, and the folks there are more knowledgeable about Compose in general.
j
> It doesn’t sound like standard behavior to me, but I could be wrong. It's standard behaviour in that all other toolkits (Win32, WPF, UWP, Qt, Cocoa, HTML5, etc) all support it and all accessibility guidelines demand it. Of course it's not standard in less mature frameworks like Compose/Flutter/SwiftUI, as it's not common in smartphone apps.
I'll look into custom focus managers, thanks
a
You don’t need a custom focus manager (although maybe that’s one way to do it). You need to listen to keypresses and call
FocusManager.moveFocus
with the correct direction.
You can obtain the focus manager via
LocalFocusManager.current
in the composition.
j
FocusManager.moveFocus can only move focus to focusable items.
focusable items are included in the tab order.
a
Yes
Not if you listen to tab presses yourself, consume the event, and do what you want to do.
j
To do that I need to listen on Tab globally, across the whole application, not just on my component.
Say I've got three buttons followed by a list followed by another button.
It's the tab press on the last button that I need to intercept, or the Shift-Tab press on the button after the list
Similarly, I can set focusProperties { next = ..., previous = ... } on these same elements, but that again requires me to add modifiers to items outside of the list.
As our app is modular, some of our enterprise clients customize it by adding UI widgets after/before the room list, so that's not really an option.
I've also tried intercepting the onFocusEvent event, to immediately move the focus to the selected list item when my list is selected, but that still doesn't allow me to move the focus back out of the list again.
As you suggested, I've now asked the question again in #CJLTWPH7S https://kotlinlang.slack.com/archives/CJLTWPH7S/p1757602989643269