React - Prevent focus going out of modal when tabbing

Viewed 9114

I have built a react modal on my own. When I press tab key while the modal is opened, the focus still goes to the background page. How to restrict the focus within the components inside the modal?

What should be the logic below?

onKeyPress (e) {
   if (e.keyCode === 9) {
       e.preventDefault();
      // logic here?
   }
}

React Modal code:

<ReactModal onKeyPress={this.onKeyPress} >
   <input type="text"/>
   <input type="text"/>
</ReactModal>
3 Answers

I found a very simple vanillaJS solution that should work in any modern browser. And can be easily adapted to any React component. It doesn't require any additional modules or complicated logic, watching keys or whatnot.

const container=document.querySelector("_selector_for_the_container_")

//optional: needed only if the container element is not focusable already
container.setAttribute("tabindex","0")

container.addEventListener("focusout", (ev)=>{
  if (!container.contains(ev.relatedTarget)) container.focus()
})

The mode of operation is very simple:

  • makes the container focusable, if not already
  • adds an event listener to the focusout event which fires when the focus is about to go outside of the container
  • Checks if the next target of the focus is in fact outside of the container, and if so, then puts the focus back to the container itself

The last check is needed because the focusout event also fires when the focus moves from one element to the another within the container.

Note: the focus can leave the page, eg the address bar of the browser. This doesn't seem to be preventable - at least according to my testing in Chrome.

I had to lock focus within a modal that we had used within a React component. I added eventListner for KEY DOWN and collected Tab and Shift+Tab

class Modal extends Component {
    componentDidMount() {
        window.addEventListener("keyup", this.handleKeyUp, false);
        window.addEventListener("keydown", this.handleKeyDown, false);
    }

    componentWillUnmount() {
        window.removeEventListener("keyup", this.handleKeyUp, false);
        window.removeEventListener("keydown", this.handleKeyDown, false);
    }

    handleKeyDown = (e) => {

        //Fetch node list from which required elements could be grabbed as needed.
        const modal = document.getElementById("modal_parent");
        const tags = [...modal.querySelectorAll('select, input, textarea, button, a, li')].filter(e1 => window.getComputedStyle(e1).getPropertyValue('display') === 'block');
        const focusable = modal.querySelectorAll('button, [href], input, select, textarea, li, a,[tabindex]:not([tabindex="-1"])');
        const firstFocusable = focusable[0];
        const lastFocusable = focusable[focusable.length - 1];

        if (e.ctrlKey || e.altKey) {
            return;
        }

        const keys = {
            9: () => { //9 = TAB
                if (e.shiftKey && e.target === firstFocusable) {
                    lastFocusable.focus();
                }

                if (e.target === lastFocusable) {
                    firstFocusable.focus();
                }
            }
        };

        if (keys[e.keyCode]) {
            keys[e.keyCode]();
        }
    }
}
Related