Jefree Sujit
Jefree Sujit

Reputation: 1586

React - Prevent focus going out of modal when tabbing

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>

Upvotes: 7

Views: 17697

Answers (3)

BenVida
BenVida

Reputation: 2312

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.

Upvotes: 4

prax
prax

Reputation: 61

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]();
        }
    }
}

Upvotes: 1

Ravindra Ranwala
Ravindra Ranwala

Reputation: 21124

Well, you can do it using a focus trap. Check out this npm module for that. Merely wrap your which contains modal with a focus-trap like this.

    <FocusTrap
              focusTrapOptions={{
                ...
              }}
            >
              <div className="trap">
                <p>
                  Here is a focus trap
                  {' '}
                  <a href="#">with</a>
                  {' '}
                  <a href="#">some</a>
                  {' '}
                  <a href="#">focusable</a>
                  {' '}
                  parts.
                </p>
                <p>
                  <button onClick={this.someCallback}>
                    Click Me
                  </button>
                </p>
              </div>

</FocusTrap>

Instead of giving you advice on implementing this, I suggest that you just don’t implement it yourself. It is hard to get right considering accessibility.

Instead, I would suggest you to use an accessible off-the-shelf modal component such as react-modal. It is completely customizable, you can put anything you want inside of it, but it handles accessibility correctly so that blind people can still use your modal.

Upvotes: 6

Related Questions