It is a trap! We got your focus and will not let him out! These words you must hear every time you have opened a modal dialog. But you wont… Modal dialog , or are an UI trick to let you do something “single” — upload a file, provide an URL, “Are you sure”, “Click a button to win $$$” and so on. Modal dialog Lightbox Focused task Usually, you shall not, and cannot not, do anything else — you are able to perform any actions only inside hightlited modal. This is true to native modals, but not usually true for DOM implementations. “Design” But, if you want to stylish your page, and your dialogs — you have to implement “modals” using the DOM API, HTML and CSS. And to do it? Cover the entire page with a shadow. Place a dialog(window, lightbox or task) above it. Simple and Usable. And here our story starts… This is screen shot from — a quite usable modal library, which will cover the one unusual thing. react-aria-modal demo Normally you cant click at elements “outside” — they are covered by shadow. “Tab-out” from a modal. But you can In this case — modal is not very “modal”. React-aria-modal is it by using . fixing react-focus-trap Best practices MDN has a perfect article about building accesseble dialog boxes. It explain WHAT YOU have to do, but not explaining — HOW. _The dialog role is used to mark up a DHTML based application dialog or window that separates content or UI from the…_developer.mozilla.org Using the dialog role In short: you have to manage keyboard focus. And next they describe how. — is a perfect solution. It passes all the tests. jQuery UI dialog But where to get a good component in React/Vue/Angular/non-jQuery world? ? No focus management. http://www.material-ui.com/#/components/dialog — Nope. https://semantic-ui.com/modules/modal.html — Nope https://react.semantic-ui.com/modules/modal — Nope https://atlaskit.atlassian.com/components/modal-dialog — Nope. https://rambler-digital-solutions.github.io/rambler-ui/#/components/Popup?_k=vs348d — 🤷♂ https://ant.design/components/modal/ — 🤷♂ http://blueprintjs.com/docs/#core/components/dialog ….this list is infinite…. is quite strange — it handles change focus by Tab, but allows to leave a Modal by Shift+Tab. Ant works well, but will return focus only to autofocus element, or first element with tabIndex. Otherwise — will not. BluePrint.js They just are using improper way to manage a focus. The is NO ideal solution. Hooray! PS: Except jQuery. It just works. Focus trap Just to recall — I’v mention the library. It can show a Modal. ! And it uses react-focus-trap to manage focus. react-aria-modal Correctly react-focus-trap is a simple React component, which will not let your leave a boundaries of . Focus Modal But then I checked the sources — I’v found that it is . not a trap That’s A Trap. I found . And emulation is the worse thing you can do. only emulation How react-focus-trap works. It will attach global keyboard event listeners. On “Tab” it will collect all* tabbable elements and manually move focus from one to another. By doing this it will control focus, and that is a goal. But all* is not all. does not include area elements, for example. And you should not emulate the browser bahevior. Tabbles How to do it in a better way? How to lock a Focus There is 3.5 good ways to do it. The best way Move all elements from body into div with tabIndex=-1. By tabindex is not working for chilren, so you might set negative tabIndex to all the elements with a script, or just set , a new html property to disable inteactions with a whole DOM-tree… which is not supported anywhere, yet (but you can use polifills). inert By the time you can set pointer-events: none, and disable user-select on top node. Just to be sure. Move modal out of that div and gave a positive tabIndex. And least but not least — you should set aria-hidden to the Div, to not let the screen reader to read anything outside the modal of the modal to prevent accidentally escaping. And on “everything” to achieve it. Screen readers should also be trapped inside aria-hidden is the only way This is native browser behavior, and anything except Modal will be untabbale in a real. PS: Not everything — you still can tab-out from a page. May be you want it? _On my current project we have some modal panes that open up on certain actions. I am trying to get it so that when that…_stackoverflow.com Keep tabbing within modal pane only The smart way Just attach a handle to a last(and first) element, and handle Tab only on the “edges” $(':tabbale:last').on('keydown', function (e) { if ($("this:focus") && (e.which == 9)) { e.preventDefault(); $(':input:first').focus(); }}); It will work, it will preserve browser behavior “between” edges… but will . ignore tabIndex and element modifications This is better that “whole” emulation, but you should ignore this way. The right way The right way is not to emulate Tab, but to just not let him out. Not let the focus out. focus-trap-react is emulation, but (a different one!) is not. react-focus-trap According to react-focus-trap the sources attaches a global listener to “focus” event. Then something got focus it will check WHO. If focus is outside modal — it will be returned to the element. first This solution is very close to the ideal, but it will always return a focus to the first element, and have no idea about tabIndex. But anyway — this is the right way. Let the focus do anything inside modal, just lock it. The Focus Lock That means — if no good solution exists — it is time to create a new solution. Lock and loaded… First I planned to write an article about KISS principle, and explain the meaning of it by comparing react-focus-trap with . But now I am unsure — it is will KISS-friendly or not.. react-focus-lock But, to say the truth — this is not Focus Trap. And even not Focus-Lock. This is Focus Jail, or Focus Wall. Focus is free inside, it just cant pass the Border Security (and Jon Snow) React-focus-lock React-focus-lock, the solution I’v build and I am going to talk about uses the last and not the best way to detect the focus change — onBlur/FocusOut event. It is not as handy as FocusIn event, as long node will first lose focus, and then new component will get it — so you have to wait before check. But.. FocusOut is internal event, as long FocusIn is external. If you can listen only on your own node — you should no it. TabIndex and the Prisoner of Azkaban But the trickiest thing is to handle tabIndex _without_ emulation tabIndex. I did not found any existing solution except focus-trap-react, which is not a solution, and yet again have to build my own. Algorithm is simple: 1. Remember the last focused item.2. On focusOut: find common parent of modal and document.activeElement get all tabbable element inside common parent. get all elements inside the Modal3. Find the difference between last focused item and current.4. If diff(current-active)>1 -> return focus to the last node.5. If current < first node -> go to the last-by-order6. If current > last node -> go to the first-by-order7. If first < current < last -> move cursor to the nearest-in-dirrection tabbable The most tricky one is #7. If you use tabIndex(dont do it!) you can have: Focus outside Modal Focus inside Modal Focus outside Modal Yet again inside.. Neat :) Most of realizations does ignore tabIndex at all :) inert Conclusion Anyway, I’v spent few hours on weekends and create a quite usable components, which can help you to lock the Pandora Box you might have. Usage is simple: <FocusLock disabled={disabled}> Something inside </FocusLock> And you can use it for React(as ), or for Vue(as ). And get a good experience in both cases. react-focus-lock vue-focus-lock Here is the demo: (lock is , or it will steal ANY focus, forcing you to close browser tab :P) disabled by default Just wrap your modal with simple component, and you will become more WAI-ARIA compatible. PS: And you will also get auto-focus out of the box :) After a year of development *-Focus-lock could lock anything anywhere, supports “focus groups”, autodetects React 16 Portals, and provides some extra helper component for your service. It is still the “best” focus-locking library, based on the “right” principles. PS! You still have to hide “everything else” with aria-hidden. Lock can’t do it for you — it is not it’s job. You also have to wrap focus trap with element with , and yet again you have to do it by yourself. role=’dialog’ You have to make everything except you modal in-accessible. Un-reachable. Inert. How to do it? block everything except the Modal, using a “shadow”, “lightbox” or experimental HTML attribute. Or (or Dom-Locky) to just disable all iterations with the rest of the page. inert React-Locky wrap everything except modal with to “hide it”. aria-hidden A good article you should read about building accessible dialogs from Google IO: _As the sites we build increasingly become more app-like it's important that the platform keep up and give component…_robdodson.me Building better accessibility primitives So this is React-Aria, the modals that “just works”. _A demo of react-aria-modal_davidtheclark.github.io react-aria-modal demo Links You just read an artile about React-focus-lock, a jail for your focus. This is Component with Single Responsibility, to help you “fix” your modals _react-focus-lock - It is a trap! A lock for a Focus._github.com theKashey/react-focus-lock And this one is for Vue.js (10 lines of code, sweet!) _vue-focus-lock - It is a trap! A lock for a Focus._github.com theKashey/vue-focus-lock PS: And dont forget about pure vanilla . Which will use focusIn event :) dom-focus-lock And don’t forget about another part of good modals — event isolation, and preventing body from scrolling, while modal is being opened. _So, JFYI, you just scrolled a page down to this place. You were able to do it. The problem is – sometimes you should…_hackernoon.com How to train a your Scroll _First of all — WHY one have to fight the scroll?_medium.com How to fight the <body> scroll