Skip to content

Allow FocusManager to return focusable elements without focusing them #8281

Open
@AlexVipond

Description

@AlexVipond

Provide a general summary of the feature here

Right now useFocusManager provides functions for focusing the next, previous, first, and last elements.

I would love to have four additional functions: findNext, findPrevious, findFirst, and findLast, which simply return the focusable element (or null) and don't attempt to focus it.

🤔 Expected Behavior?

useFocusManager provides findNext, findPrevious, findFirst, and findLast for finding focusable elements, without actually focusing them.

😯 Current Behavior

useFocusManager only provides functions for focusing elements, not for checking presence/absence of focusable elements.

💁 Possible Solution

Lightly refactor createFocusManagerForScope to extract and return findNext, findPrevious, findFirst, and findLast (See PR that closes this issue)

🔦 Context

Alert

My immediate use case is a custom alert/alertdialog component with the following logic:

  • If the alert content does not contain a focusable element, it should disappear after 5 seconds (it's purely informative and static)
  • If the alert content contains a focusable element, it should never automatically disappear (because it requires/enables user action)

I can imagine this UX being useful in any alert or alert-like component that wants to give the user extra time to take an action. It wouldn't be good UX to call focusManager.focusFirst here, because assistive tech users should be allowed to ignore certain alertdialogs and stay where they are in the page.

An alternate solution is to add a prop to my alert component that lets devs tell the component whether or not content contains a focusable element. But this is a tedious DX, and over time, it's virtually impossible to make sure the prop value stays correct and in sync with other code.

Tablist

In the past, I have also written and used a similar feature for a custom tablist component, specifically to meet this tablist ARIA Authoring Practice:

When the tab list contains the focus, moves focus to the next element in the page tab sequence outside the tablist, which is the tabpanel unless the first element containing meaningful content inside the tabpanel is focusable.

In other words, the tabindex of the tab panel should be 0 if findFirst(...) is unable to find a focusable element inside the panel.

In React, I would likely just use React Aria's tablist and not worry about these details, but for more custom implementations, the focusable finding feature could be useful.

💻 Examples

function CustomAlert() {
  return (
    <FocusScope>
      <CustomAlertChild />
    </FocusScope>
  )
}

function CustomAlertChild() {
  const focusManager = useFocusManager();

  const [timeout, setTimeout] = useState<'never' | number>('never');

  useLayoutEffect(() => {
    setTimeout(
      focusManager.findFirst()
        ? 'never'
        : 5000
    )
  }, [focusManager])

  // ...other alert logic

  return (
    <div>
      ...
    </div>
  )
}

🧢 Your Company/Team

Just me, independently

🕷 Tracking Issue

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions