Skip to content

[react-aria] FocusScope with contain prevents programmatic scrolling when prefers-reduced-motion: reduced and scroll-behaviour: auto is givenΒ #6967

Open
@noahbald

Description

@noahbald

Provide a general summary of the issue here

Consider the following code example

<FocusScope contain>
  <ScrollableContainer>
    <p id="test">Hello, world!</p>
    {/** Lots of text */}
    <a href="#test">Scroll back to top</a>
  </ScrollableContainer>
</FocusScope>

The issue seems to only occur when contain is set to true on the FocusScope

πŸ€” Expected Behavior?

I'd expect that when I click on a hash anchor link, it should be consistent with the default browser behaviour, scrolling to the targeted element.

😯 Current Behavior

If I have my acessibility settings to have prefers-reduced-motion set to reduce, with the scroll behaviour of the given container being auto then clicking on the "Scroll back to top" link does nothing.

πŸ’ Possible Solution

Adding an onclick event for the link similar to the following can resolve the issue. This won't work in Safari, the FocusScope will take control back to the link and prevent scroll.

(event) => {
  const href = e.currentTarget.href;
  if (!href.startsWith('#')) return;

 document.querySelector(href)?.scrollIntoView();
}

Though this isn't a reasonably scaleable solution.

I've tried using focus() to force scroll within the FocusScope but I don't like to think what the a11y implications will be.

const scrollToHashHref = (event: React.MouseEvent<HTMLAnchorElement>) => {
  const href = event.currentTarget.getAttribute('href')
  if (!href || !href.startsWith('#')) return

  // Href targets an id, so let's try scroll to that element
  const ref = document.querySelector(href)
  if (!(ref instanceof HTMLElement)) return

  // We need to ensure the element is focusable, focus on it, and then restore it's original focusability
  // - FocusScope will refocus on the link, preventing scroll
  // - FocusScope will prevent using `focus()` on non-focusable elements in Safari
  const initialTabIndex = ref.getAttribute('tabindex')
  ref.setAttribute('tabindex', '-1')
  ref.focus()
  if (initialTabIndex == null) {
    ref.removeAttribute('tabindex')
  } else {
    ref.setAttribute('tabIndex', initialTabIndex)
  }
}

πŸ”¦ Context

No response

πŸ–₯️ Steps to Reproduce

Using react-aria

export default function App() {
  return (
    <>
      <FocusScope contain>
        <div style={{ height: 40, overflow: 'auto' }}>
          <p id="wont">This won't be scrolled to</p>
          <p>Lorem ipsum</p>
          <a href="#wont">Scroll back to top</a>
        </div>
      </FocusScope>
      <FocusScope contain={false}>
        <div style={{ height: 40, overflow: 'auto' }}>
          <p id="will">This will be scrolled to</p>
          <p>Lorem ipsum</p>
          <a href="#will">Scroll back to top</a>
        </div>
      </FocusScope>
    </>
  );
}

Version

3.34.1

What browsers are you seeing the problem on?

Chrome

If other, please specify.

No response

What operating system are you using?

MacOS

🧒 Your Company/Team

No response

πŸ•· Tracking Issue

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions