Focus and Scrolling to Refs and React
6/21/2020
Whenever we introduce changes in the DOM that show, hide, or affect an element's display, we need to make sure that screen readers pick up on it and that the user can interact with it in a familiar, standardized way. For example, what if an API fails and we need to notify the user an error occurred? Let's say the decision is to show an error dialog box with a message and a close button at the top of the page when the error triggers - kind of a common pattern.
If this dialog is outside the viewport when it shows up, we need to make sure that we scroll the user to it. We also want to allow the user to close it with their keyboard and not make them tab a hundred times to close it. To solve the first issue, we could try and mess with media queries and scroll position to see if we're near the top of the page and conditionally scroll. We could even try using Intersection Observer. Both of these solutions require adding more code and complexity to our application. But when we try to solve for the second issue - making sure it's tab-friendly - we automatically get the benefit of the browser auto-scrolling to the focused element.
Adding focus doesn't take care of all accessibility issues in our case, we would still need to add the appropriate aria-attributes and make sure they are toggled accordingly. It does address the original concern of scrolling to the top of the page conditionally, though, with some added accessibility benefits. In our case, thinking of accessibility first (using focus rather than scroll) saves time and more importantly allows us to rely on native browser behavior rather than scroll checks (although Intersection Observer really is super fun).
Focusing Refs in React
In React, we need a create a ref in order to interact with a DOM element that is rendered. We can access its current
property in order to make sure it exists and act accordingly.
Here's how I focused the dialog's close button in the example below with a ref:
- Created some state for whether or not the dialog is shown
- Created a ref that I passed to the button that closes the
dialog
- Added a
useEffect
to run whenever the ref updates
Here's the example - a bit contrived, but hopefully it helps show off how using .focus()
only scrolls when it needs to as you scroll down the page and simulate an error dialog: