A CSS Approach to Trap Focus Inside of an Element
I recently read this article by Keith Grant which introduced the newly arrived <dialog>
. Excited by this new UI element, I immediately sat down to experiment with it to see how it can be used effectively as a modal — the most common use of it. While experimenting, I discovered a neat CSS trick on how to trap focus within the <dialog>
element, a common accessibility requirement for modals, and a notoriously difficult one.
What is focus trapping?
First, a quote from the W3C documentation regarding what should happen following a key press inside a dialog:
Tab:
- Moves focus to the next tab-able element inside the dialog.
- If focus is on the last tab-able element inside the dialog, moves focus to the first tab-able element inside the dialog.
Shift + Tab
- Moves focus to the previous tab-able element inside the dialog.
- If focus is on the first tab-able element inside the dialog, moves focus to the last tab-able element inside the dialog.
To summarize, when inside a dialog, pressing Tab
or Shift+Tab
should cycle the focus within the dialog only—amongst the focusable elements inside the dialog.
This makes sense because when a dialog is open, a user is interacting only inside it and allowing the focus to escape outside the dialog would essentially be mixing contexts and possibly create a state where the user doesn’t know which element is in focus.
So, going back to the idea of a modal, our expectation would be that tabbing inside of the modal would only focus on elements inside of the modal. Anything outside of the context of the modal would be out of scope because the tab is only concerned with what is inside of it. This is what we mean by focus trapping.
An implementation with JavaScript
If we were to implement focus trapping inside a <dialog>
, the most common approach would be to do the following when the dialog opens:
1. Grab all the focusable/tappable elements inside the dialog.
2. Listen for Tab
and Shift+Tab
keypresses and manually focus the next or previous element, respectively.
3. If the keypress happens on the first focusable element, then focus the last focusable element in the chain and vice versa.
This way, we create a loop on focus as the user presses Tab
or Shift+Tab
. See this W3C code snippet as an an example of how this might be approached with JavaScript. You’ll see it’s quite a bit of JavaScript.
Enter :focus-within
Back to my experiment with the new <dialog>
element. When thinking about focus trapping, a CSS pseudo class (also very recent in browsers) immediately came to my mind : :focus-within
.
If you have not heard about that before, it represents an element that has received focus or contains an element that has received focus. So, for example, you have a <div>
and inside of it is an input
element. If you want to style that <div>
when the contained input
has focus, you can do it like so:
div:focus-within {
border: 2px solid red;
}
See the Pen :focus-within by Geoff Graham (@geoffgraham) on CodePen.
The CSS trick to focus trapping
Let’s exploit :focus-within
and CSS transitions to implement a basic focus trap inside of a <dialog>
element.
To summarise, here is how the trick works. When the focus is not within the dialog (and the dialog is open), we:
- trigger a CSS transition
- detect that transition completion in JavaScript
- focus the first element in the dialog
But, first let’s get set up. Here’s the basic dialog and opening functionality:
<button id="button">Open dialog</button>
<dialog id="modal">
<form action="">
<label>
<input type="text" /> Username
</label>
<label>
<input type="password" /> Password
</label>
<input type="submit" value="Submit" />
</form>
</dialog>
button.onclick = () => {
modal.showModal();
}
There we go. Clicking on the button should open our dialog. Just this much code required to make a basic working modal using the new <dialog>
element!
See the Pen Dialog focus trapping with CSS by Kushagra Gour (@chinchang) on CodePen.
Limitations
This is a quick experiment I did to create a working proof-of-concept of focus trapping with the :focus-within
pseudo class. It has several limitations compared to dedicated JavaScript solutions to achieve this. Nevertheless, something is better than nothing!
Here are a couple of things this implementation lacks:
- According to W3C guidelines, the focus should cycle on the focusable element. But we are always focusing on the first
input
element. This is because without writing more JavaScript, we cannot know whether the focus was lost from the first or last element. - We are always focusing back to the first
input
element. But there are many more focusable HTML elements that might be present beforeinput
or maybe there is notinput
element at all inside the modal. Again, full-fledged JavaScript solutions detect and maintain a list of all focusable elements and focus the right one.
Better (JavaScript) implementations of focus trapping
- As mentioned earlier, one working example is available inside the W3C documentation itself.
- Here is another implementation by Rodney Rehm that also listens for tab.
- Greg Kraus has a library that achieves this. His implementation maintains a list of selectors for all valid focusable elements.
- One more lightweight library to create accessible modals.
That is all for this experiment. If you liked this trick, you can follow me on Twitter where I share more articles and side projects of mine.
The post A CSS Approach to Trap Focus Inside of an Element appeared first on CSS-Tricks.
LEAVE A REPLY
You must be logged in to post a comment.