cribbble

How to trap focus with JavaScript

avatar
Ward Poel

When you're showing a modal inside your application, you most likely want to keep the focus inside the modal. With this function that's possible.

const TAB = 9;
const FOCUSABLES =
	'a[href], area[href], input:not([disabled]):not([hidden]):not(.hidden), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), iframe, object, embed, *[tabindex]:not(*[tabindex="-1"]), *[contenteditable]';

export default function trapFocus(event) {
	if (event.keyCode !== TAB) return;

	let element;
	let focusables = Array.from(event.currentTarget.querySelectorAll(FOCUSABLES));
	focusables.sort((f1, f2) => {
		return (f1.tabIndex ?? 0) - (f2.tabIndex ?? 0);
	});

	if (focusables.length === 0) {
		element = event.currentTarget;
	} else {
		let index = focusables.indexOf(document.activeElement);
		if (event.shiftKey) {
			if (index === -1 || index === 0) {
				element = focusables[focusables.length - 1];
			} else {
				element = focusables[index - 1];
			}
		} else {
			if (index === focusables.length - 1) {
				element = focusables[0];
			} else {
				element = focusables[index + 1];
			}
		}
	}

	if (element) {
		event.persist();
		event.preventDefault();
		event.stopPropagation();
		element.focus();
	}
}

Usage

When opening the modal, you must focus programmatically the modal container. Now your focus will always stay inside the modal, until you close it.

// JSX
let modalRef = useRef();

useEffect(() => {
		modalRef.current.focus();
}, []);

return <div ref={modalRef} onKeyDown={trapFocus} tabIndex={-1}>
	{/* Your modal content */}
</div>

If you have any questions, I'm @WardPoel on Twitter.