When developing applications, front-end vendors rarely pay attention to how the user will use the keyboard functions provided by the browser. I am not an exception, but one day I was given a task regarding UX and transitions by pressing “Tab” and “Shift + Tab”.
The essence of the task is transparent and clean: there is an interface, the layout of which is displayed below. Conceptually, 1 page can contain 2 different forms and the requirement was that “running with Tabs does not go from 1 form to another”.
Everything would be fine if the browser could "natively" block focus in forms. An example is presented in the figure below, where the orange border indicates the current element and the previous one in gray.

As you can see, the "native" behavior does not meet the requirements. So, let's solve this problem. The solution is not complicated, so consider it.
It would be ideal if there were some “gates” that would not allow “jumping out” of focus from the last (with “Tab”) or the first (with “Shift + Tab”) element with
“tabindex” or supporting focus by default. So, the essence is simple: our “gates” are hidden “input elements”, which receive an “event” event as an argument during the “onFocus” event and return focus to the element from which it came. Illustration below.
Getting the previous element is feasible using the "relatedTarget" property of the "event" object. Visualization of the solution below.

And here is the code itself. It is worth noting that there is no "ES6 +" syntax, since the idea is to support the code with different browsers without connecting different "transpilers" like Babel.
function getGateInput(handleTabOut) { var input = document.createElement("input");
There is nothing complicated here: an “input” is created, styles are set that “hide” our “gates”. Here, “display: none” is not used, since the browser does not focus “Tabs” on such elements. Due to this behavior, it is required to make the element transparent and move it out of the browser window.
function getTabOutHandler(element, GATES) { return function(event) { var relatedTarget = event.relatedTarget || event.fromElement; var target = event.target; var gatesTrapped = target === GATES[0] || target === GATES[1]; if (gatesTrapped && isChild(relatedTarget, element)) { event.preventDefault(); relatedTarget.focus(); } }; }
To return focus to the previous item, use getTabOutHandler. This is
the HOC . Its first argument is our container (the one around which we set the “gate”), and the second it expects an array of “gates” that we created using getGateInput. This function returns an event handler that works according to the principle described above.
In order for the focus to go into the container, we need to open and close the “gate”. We will do this by setting the
“tabindex” attribute. (-1 - do not focus with Tabs, 0 - focus according to the stream)
function moveGates(open, GATES) { GATES[0].setAttribute("tabindex", open ? -1 : 0); GATES[1].setAttribute("tabindex", open ? -1 : 0); }
To control the gates, we will install a handler that will “listen” to pressing Tab (code 9) and if the focused element (activeElement) is inside the container, close the “gate”, otherwise open it.
window.addEventListener("keydown", function(event) { if (event.keyCode === 9) { if (isChild(document.activeElement, element)) { moveGates(false, GATES); } else { moveGates(true, GATES); } } });
Total
A method for locking focus in a form was considered, which consists in returning focus to a previous focused element. To "catch" the focus, we used hidden "input elements", the focus of which was controlled using the
"tabindex" . The above code is part of the
tab-out-catcher library that I wrote to solve my problem. Examples of use can be found
here . There is also a
solution for React applications.