Trap keyboard focus within a Modal

Most React frameworks like Antd do a good job of trapping keyboard focus within a modal. If there is a textbox and some buttons, tabbing from the textbox takes you to the button. Tabbing from the buttons takes you back to the textbox. The keyboard focus never goes to the controls in the background. This is what I mean by trapping keyboard focus within a Modal.

Scenario where keyboard focus leaves the Modal

As an example, consider this code.

import { useState } from "react";
import { Modal } from "antd";
import "./styles.css";
import "antd/dist/antd.css";

export default function App() {
  const [show, setShow] = useState();
  
  function toggleModal() {
    setShow(!show);
  }

  return (
    <div className="App">
      <input type="text" />
      <button onClick={toggleModal}>Toggle</button>
      <Modal visible={show} onCancel={() => setShow(false)} footer={null}>
        <input type="text" tabIndex={1} />
        <button
          onClick={() => setShow(false)}
          tabIndex={3}
        >
          Cancel
        </button>
        <button onClick={() => setShow(false)} tabIndex={2}>
          Save
        </button>
      </Modal>
    </div>
  );
}

In the code above, there is a textbox and a toggle button. Clicking on the toggle button opens up or closes a modal. The modal has a textbox and two buttons – Cancel and Save. The natural order when the user tabs from the textbox (within the Modal) is Cancel button, Save button, Close icon and then back to the textbox. Keyboard focus never escapes the Modal. For this example, I am using the Modal component from Antd. Antd has done a good job of trapping the keyboard focus within the Modal.

However, the default behaviour changes when we specify a tabIndex to the controls within the Modal. By specifying a tabIndex, we want to ensure that tabbing from the textbox goes to the Save button and not the Cancel button. After we specify the tabIndex, tabbing on the Cancel button takes the user to the textbox in the background. In short, keyboard focus leaves the Modal and moves to the background.

Solution to trap keyboard focus within the Modal

Whenever we specify tabIndex, we have to write some more work-around code to ensure that focus never leaves the Modal. The complete work-around code is in this CodeSandbox.

Trap keyboard focus within Modal
Trap keyboard focus within Modal

The solution is to handle onKeyDown event of the Cancel button. Within the onKeyDown event, if the user tabs, manually bring the focus back to the textbox within the modal.

function handleKeyDown(e) {
    if (e.key === "Tab") {
      setTimeout(() => {
        if (inputRef.current) {
          inputRef.current.focus();
        }
      }, 0);
    }
  }

The reason we have a setTimeout is because we want the keyUp event to be fired before our manual focus code. As a result, there is a flicker in the background textbox as focus momentarily goes to the background and then our manual focus code causes the focus to return to the textbox within the Modal.

Related Posts

Leave a Reply

Your email address will not be published.