Keyboard support for accessible modals
Creating a focus-lock hook in React.
Accessibility is one aspect of web development that gets overlooked too quickly. People with disabilities cannot use our application or struggle to get anything done.
Organizations do not realize that they cut out a considerable user base simply by not making their applications accessible, which leads to revenue loss and can risk a lawsuit in some cases.
We should rethink making our applications much more accessible.
In this tutorial, I will be covering keyboard support for an accessible modal component.
The w3.org specifies particular keyboard supports for a modal, as shown below.
One of the things to note is that the tab functionality must be within the dialog. The browser doesn't have an API that lets us do this. We have to handle ourselves. Else, we have the issue as below shown below;
We can create a focus-lock hook that locks the user within the modal.
The useFocusLock can also be used for other use-cases apart from a modal, and hence it will be generic.
Let's get started;
We will grab all of the focusable HTML tags.
We then query all of the focusableTags within the modal (or wrapper) and save them in the ref current (we could have used a state).
The hook function takes wrapper props which should be a ref. All the focusable Tags query is done within the wrapper element and saved in a focusableElements ref.
N.B: We don't need to pass in the wrapper as a dependency to our useEffect refs' update doesn't trigger a re-render.
InitialFocus within wrapper
Let's make sure we can change the initialFocus when we call the hook or default to the first focusable element in the modal (or wrapper component).
Keyboard listeners
Now, when we call the useFocusLock hook and pass the wrapper into it, we have access to all focusableElements within the wrapper. We can handle the keyboard support tabs by adding event listeners to the first and last focusable elements.
Since we want to ensure that when we Tab within the wrapper tags, when we get to the lastFocusableElement and not tabbing + the shift key, we will return to the firstFocusableElement. That way, we are within the boundaries of the wrapper focusable elements.
Also, when we shift + Tab, we will do the vice-versa. When we get to the firstFocusableElement, we want to return to the lastFocusableElement.
Also, we are making sure that the firstFocusableElement and lastFocusableElement are the current activeElement in the document.
We are also calling and cleaning up the event listener in the useEffect.
We have satisfied everything we need to meet the keyboard support for Tabs as shown below;
One more thing to handle is the escape key event handler. We can also add to focus-lock or create a separate event handler since the focus-lock hook should be reusable and not tied to modal use-case alone. I used the latter approach.
Now we have all of the parts needed to support all of the keyboard requirements fully.
The full hook we have is below;
Thank you for reading. Let me know your thought if you find this helpful.