Using JavaScript to trap focus in an element

Published on by Hidde de Vries in code .

For information on how to accessibly implement the components I’m working on, I often refer to WAI-ARIA Authoring Practices specification. One thing this spec sometimes recommends, is to trap focus in an element, for example in a modal dialog while it is open. In this post I will show how to implement this.

Some history

In the early days of the web, web pages used to be very simple: there was lots of text, and there were links to other pages. Easy to navigate with a mouse, easy to navigate with a keyboard.

The modern web is much more interactive. We now have ‘rich internet applications’. Elements appear and disappear on click, scroll or even form validation. Overlays, carousels, AJAX… most of these have ‘custom’ behaviour. Therefore we cannot always rely on the browser’s built-in interactions to ensure user experience. We go beyond default browser behaviour, so the duty to fix any gaps in user experience is on us.

One common method of ‘fixing’ the user’s experience, is by carefully shifting focus around with JavaScript. The browser does this for us in common situations, for example when tabbing between links, when clicking form labels or when following anchor links. In less browser-predictable situations, we will have to do it ourselves.

When to trap focus

Trapping focus is a behaviour we usually want when there is modality in a page. Components that could have been pages of their own, such as overlays and dialog boxes. When such components are active, the rest of the page is usually blurred and the user is only allowed to interact with our component.

Not all users can see the visual website, so we will also need to make it work non-visually. The idea is that if for part of the site we prevent clicks, we should also prevent focus.

Some examples of when to trap focus:

In these cases we would like to trap focus in the modal, alert or navigation menu, until they are closed (at which point we want to undo the trapping and return focus to the element that instantiated the modal).


We need these two things to be the case during our trap:


In order to implement the above behaviour on a given element, we need to get a list of the focusable elements within it, and save the first and last one in variables.

In the following, I assume the element we trap focus in is stored in a variable called element.

Get focusable elements

In JavaScript we can figure out if elements are focusable, for example by checking if they either are interactive elements or have tabindex.

This gives a list of common elements that are focusable:

var focusableEls = element.querySelectorAll('a[href], button, textarea, input[type="text"], input[type="radio"], input[type="checkbox"], select');

This is an example list of common elements; there are many more focusable elements.

Save first and last focusable element

This is a way to get the first and last focusable elements within an element:

var firstFocusableEl = focusableEls[0];  
var lastFocusableEl = focusableEls[focusableEls.length - 1];

We can later compare these to document.activeElement, which contains the element in our page that currently has focus.

Listen to keydown

Next, we can listen to keydown events happening within the element , check whether they were TAB or SHIFT TAB and then apply logic if the first or last focusable element had focus.

var KEYCODE_TAB = 9;

element.addEventListener('keydown', function(e) {
    if (e.key === 'Tab' || e.keyCode === KEYCODE_TAB) {
        if ( e.shiftKey ) /* shift + tab */ {
            if (document.activeElement === firstFocusableEl) {
        } else /* tab */ {
            if (document.activeElement === lastFocusableEl) {

Alternatively, you can add the event listener to the first and last items. I like the above approach, as with one listener, there is only one to undo later.

Putting it all together

With some minor changes, this is my final trapFocus() function:

function trapFocus(element, namespace) {
    var focusableEls = element.querySelectorAll('a[href], button, textarea, input[type="text"], input[type="radio"], input[type="checkbox"], select'),
        firstFocusableEl = focusableEls[0];  
        lastFocusableEl = focusableEls[focusableEls.length - 1];
        KEYCODE_TAB = 9;

    element.addEventListener('keydown', function(e) {
        var isTabPressed = (e.key === 'Tab' || e.keyCode === KEYCODE_TAB);

        if (!isTabPressed) { 

        if ( e.shiftKey ) /* shift + tab */ {
            if (document.activeElement === firstFocusableEl) {
        } else /* tab */ {
            if (document.activeElement === lastFocusableEl) {


In this function, we have moved the check for tab to its own variable (thanks Job), so that we can stop function execution right there.

Further reading

See also Trapping focus inside the dialog by allyjs, Dialog (non-modal) in WAI-ARIA Authoring Practices and Can a modal dialog be made to work properly for screen-reader users on the web? by Everett Zufelt.

Thanks Job, Rodney, Matijs and Michiel for your suggestions!

Update 13 August 2018: examples now use vanilla JavaScript instead of jQuery.

Comments & mentions (7)

Roel Van Gils 30 Jan 2017 13:57:48

Perhaps you should mention in the article that this technique only works reliably for (sighted) users who rely on keyboard navigation.

Screenreader users typically use a virtual focus to get around on a page. (⌃ + ⌘ + Arrow keys when using Voiceover, for example).

Johan Ronsse 01 Feb 2017 12:50:23

Very interesting.

My theory is that if what is “behind” the { modal/popover/interactive element that has its own focus cycle} can be put in a separate container, and we temporarily remove it from the DOM flow, we can work around this whole problem.

A screenreader wouldn’t even see it.

But that’s just an idea. I have some more reading to do.

Ari Picker replied: I implemented my first focus trap. It worked but had a bit of jank.
Luckily I came across Hidde de Vries; his article is great.…
Danielle Fuechtmann replied: Designing ethically is essential; here are some great ways to apply ethics to front-end web development:…
Lena C. replied: How to trap focus in a modal dialog… #frontend #a11y
Bobby 14 Nov 2018 02:22:10

Great example. namespace is not used.

Matt T 11 Feb 2019 16:02:00

This example is nice and concise but actually seems ineffective. You can’t actually tab to the first and last item directly because the focus is then skipped to the next wrapped around item. You would have to detect when the focus it’s outside the element and then trigger this trap, because the tab event handling occurs after the browser has already moved the focus itself.

Leave a comment
Posted a response to this?

This website uses Webmentions. You can manually notify me if you have posted a response, by entering the URL below.