Reputation: 1586
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
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:
focusout
event which fires when the focus is about to go outside of the containerThe 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
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
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