Sometimes when you're working on managing focus,
it can be possible to get a little carried away.
Let me show you an example.
So here I've got an autocomplete widget.
I can press the Tab key to focus it.
I can start typing, which will give me this list of city names.
I can press the down key to focus one.
And then can press Tab again to complete the selection.
Now, the next thing I want to do is move to this next field by pressing Tab.
So I'll hit Tab once more.
Hm, and it looks like I'm not going anywhere.
I think what's happening is,
this autocomplete thinks I'm still trying to finish my complete action.
So no matter how many times I press Tab I'm just sort of stuck here.
This is what's known as a keyboard trap.
And it can be very frustrating for users.
In fact, Section 2.1.2 of the WebAIM checklist specifically calls this out.
Stating, the keyboard focus should never be locked or
trapped at any one particular page element.
The user should be able to navigate to and
from all page elements using just their keyboard.
But strange as it may seem,
there are times when this behavior is actually desirable.
Take for instance, the modal window.
Now, normally when a modal window is displaying on screen,
we don't want the user to be able to access any of the content behind it.
Oftentimes developers will add an overlay to cover the page, but
that doesn't stop keyboard focus from accidentally traveling
outside of the model.
Here, I'll tab through this dialog.
So I'll press the Tab key, which will focus the Decline button.
I'll tab again, which focuses the Accept button.
And I tab one more time.
No, it looks like our focus has now moved behind the modal window.
Now in instances like this what we want is a temporary keyboard trap to
ensure that we track focus within the modal wall it's displaying.
And then that we can restore it to the previously focused item once the model
is closed.
This can be a tricky technique to implement, and
while there are proposed ideas on how to make it easier for developers.
For instance, like this dialogue element here,
they don't currently have widespread browser support.
Meaning, we're going to have to roll up our sleeves and
implement this ourselves.
So here's an example of what I'm going to try to build.
I've got a little open modal button up here in the top corner.
And when I focus it and hit Enter, I get a modal dialog.
I can tab through it, and the focus is trapped.
I can hit that button and it'll close it.
I can open it again, and I can hit Escape or click the background, and
it'll close it and restore focus to that button.
So this is what I'm going to work on right now in my code editor.
So, switching to my HTML, I'll start off by creating a button element,
which I'm going to call modal-toggle.
That's going to be that little Open Modal button you saw up there
in the corner.
Then I'm going to have a div,
which will hold all of the content from my modal window.
I'm going to give it that class modal.
Inside of here I've got some text.
And I've got some input fields for the user to sign up for our newsletter.
And lastly, we've got a little sign up button down here,
which we're going to call signup.
Clicking this will close our middle window.
We've also got a modal overlay down here at the bottom.
That's going to be that sort of gray background.
Clicking that should also close the modal.
Okay, so that's our HTML markup, now let's look at our JavaScript.
Over in our JavaScript, I'm going to start by creating this variable
called focusedElementBeforeModal.
This is where I'm going to store a reference to the element that had focus
before we open the modal.
When we close the modal, we want to restore focus back to this element.
Then I'm going to use document.querySelector to select both my
modal and my overlay.
I'm also going to use querySelector to select my modalToggle button.
And I'm going to add an eventListener to modalToggle to listen for
the click event, which is going to run this openModal function.
Inside of the openModal function we're going to take that focusedElement
BeforeModal reference and we're going to grab the document.activeElement and
store it there.
We're going to listen for keydown events.
And call this trap keyboard function, which I'll get to in a moment.
We're also going to listen to indicators for us to close our modal.
So, if someone clicks on modalOverlay, we're going to close the modal.
And if someone clicks on the signupBtn that's contained within the modal,
we're also going to close the modal.
And the next thing we're going to do, is we're going to try and
figure out all the focusable children that are inside of the modal dialogue.
To do this I'm going to create this huge query string
of potentially focusable elements.
And I'm going to call modal.querySelectorAll
using that string.
So this should give me a grouping of everything within my modal
window that could possibly receive focus.
And what I want to do,
is I want to figure out what the first element in that group is.
And what the last element in that group is.
And the idea here is these can act sort of like sentinel elements.
So, if the user is tabbing through the modal window and
they reach the last focusable element or the lastTabStop.
And they press Tab again, we're going to loop them around and
put them on the firstTabStop.
Similarly, if they're on the firstTabStop, and they press Shift+Tab,
we're going to loop them around and put them on the lastTabStop.
Next, we're just going to tell the modal window to display itself.
So we'll set its style to display block.
And we'll also set the modalOverlay to display block.
And then we're going to focus that firstTabStop child inside of it.
Now from this point on, if the user starts to press a key on their keyboard,
we're going to check to see if they've hit the Tab key.
Now using a modifier key, they could have hit Shift+Tab.
And if they've hit Shift + Tab, what we're going to do,
is we're going to first figure out, are they currently on the firstTabStop?
If they are, let's wrap them around to the lastTabStop and call focus.
If they haven't hit a modifier key and they've just hit Tab by itself,
then we'll sa, are they currently on the lastTabStop?
If they are, we're going to wrap them around to the firstTabStop and focus it.
Now we're also going to add a listener for the Escape key.
So if they've hit the Escape key, we're just going to go ahead and
close the modal.
The closeModal method is pretty straightforward.
We're going to tell the model to display none.
We'll tell the overlay to display none.
And then we'll use that reference for that first focused element.
And we're going to call focus on it again to restore focus.
So in this case, it'll restore focus back to that open model toggle button.
Now I realize this is a ton of code.
I sort of think of the accessible model window as
one of the boss battles in accessibility.
So, what I want you to do in the next lesson is use some of this
code to implement a modal window yourself.
And that way you'll have a deeper understanding of how all of this works.