Skip to content

Focus management RFC #104

Closed
Closed
@devongovett

Description

@devongovett

This is an RFC for an RFC (ooh so meta 🤯). I'd like to get feedback from the React core team and the community on whether this is something that React should even handle before I do the work to come up with an API and write a formal RFC. Please let me know what you think!

Focus management

Focus management is the programmatic movement of keyboard focus in an application in response to user input, such as mouse, touch, or keyboard interactions. Implementing keyboard support for components and applications is imperative for accessibility, and not enough web applications implement this properly today. See the ARIA Practices document for more information about focus management and keyboard interfaces.

In React, implementing many common components is very easy, but implementing those components with proper support for accessibility and focus management is still quite difficult. Applications that do implement support typically use a component library which has taken the time to get this right. However, implementing such a library correctly is very challenging and time consuming, and I think React could enable library authors to handle focus management in an easier way.

Challenges

I work on the React implementation of our design system at Adobe. One of our main jobs is to implement a component library with proper support for accessibility, and we have faced some challenges while trying to achieve this goal.

Refs Everywhere

Refs are meant as an escape hatch from the declarative React model to make things happen imperatively, but we've ended up having one or more in almost every component in our library. The main reason we have refs is for focus management. We need to focus particular elements in response to events like keyboard navigation with arrow keys, etc. In addition, sometimes we need to find children of refs with querySelector and other DOM methods in order to focus the correct elements, e.g. elements inside child components. This feels pretty unclean, and un-reacty to us and I think there could be a more declarative API that React could enable.

Global focus state

The imperative focus management API in the DOM is global: only one element can be focused at a time. Calling node.focus() immediately causes all other nodes to lose focus, and the new node to gain focus. Therefore, components basically fight one another for focus. In reusable components, this is a challenge since they don't know what other components are on a page. There is no way for a component to "remember" what was focused before, so that focus can be restored to it later without some manual work in every component. This will be described more below in some of the other sections. I think React components could potentially do this automatically.

Roving tab index

Keyboard users typically navigate between components with the tab key, which changes the focused element. However, composite components such as lists, grids, trees, etc. should only appear in the tab sequence in an application once. It would be annoying if you had to tab through every item in a long list just to get past the entire list to the next element afterward. Therefore, you tab to the list, and if you want to navigate within the list you use the arrow keys, and otherwise you can tab to the next element after the entire list.

The roving tab index pattern is one way to solve this problem, but basically it involves each component being able to "remember" what was focused last, so that if a user tabs away from a list and then back, the list item they were on before is re-focused rather than the first item or the list itself. I think a focus management API in React could potentially help make this problem easier to solve.

Focus Trapping

Focus trapping is important for modals and other popups. It prevents users from tabbing or focusing elements outside a particular region. For example, if a user tabs to the last element in a modal dialog, when they hit tab again the first element of the dialog should be focused instead of something outside. This is currently impossible to implement in a general way in React without manual DOM querying and imperative focusing. I think a focus management API in React could help solve this.

Restoring focus

When you close a dialog or popover, focus should be restored to whatever had focus before the modal opened. This requires us to remember what was focused last, and when the component unmounts focus that element again. I think this should also happen automatically.

Why should this be in React?

We have experimented with various abstractions to make the above easier in user space, but have come to the conclusion that it would be better implemented in React itself. I haven't come up with an exact API that I think React should adopt yet (the purpose of this issue is to get feedback first), but generally, I think components should have a notion of whether they are in focus, and should be able to declaratively specify which of their child elements should have focus, and lock focus to within the component subtree. This has turned out to be difficult to implement in user space without access to the full React tree (only direct component children).

Most of the above points talked about the web, but I think an API in React core for this could be useful for other targets as well, such as React Native. React Native also has imperative APIs for focus management at the moment I think, so perhaps this could be valuable there as well.

Additionally, making implementation of proper accessibility patterns much easier for the entire React community could have a huge effect on the quality of modern web applications. Not everyone is using a component library that has invested as much time as we have into supporting accessibility. If more of this happened in React itself, then there would be less work for library authors and more people would be able to put in the effort.

Thoughts?

Sorry for the long issue. If you have thoughts or feedback please let me know. Is this something the React team and community would be interested in? If so, I'll work on an RFC for an API. If anyone else has ideas or has experience with this, please tell me in the comments!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions