Skip to main content
Traps keyboard focus inside a container element while enabled. When the user presses Tab or Shift+Tab, focus cycles through only the focusable elements within the container. Essential for modals, drawers, and other overlay patterns to maintain accessibility.

Import

import { useFocusTrap } from '@kivora/react';

Usage

import { useRef, useState } from 'react';
import { useFocusTrap } from '@kivora/react';

function Modal({ isOpen, onClose }) {
  const modalRef = useRef<HTMLDivElement>(null);
  useFocusTrap(modalRef, isOpen);

  if (!isOpen) return null;

  return (
    <div ref={modalRef} className="modal">
      <h2>Modal Title</h2>
      <button>First Button</button>
      <input type="text" placeholder="Enter text" />
      <button onClick={onClose}>Close</button>
    </div>
  );
}

Parameters

ref
RefObject<T | null>
required
A React ref object pointing to the container element that should trap focus.
enabled
boolean
required
Whether focus trapping is active. Typically tied to the open/closed state of a modal or overlay.

Returns

void - This hook does not return a value.

Behavior

  • Auto-focus: When enabled, automatically focuses the first focusable element
  • Tab cycling: Tab moves forward through focusable elements, wrapping to the first when reaching the last
  • Shift+Tab cycling: Shift+Tab moves backward, wrapping to the last when at the first
  • Focus restoration: Returns focus to the previously focused element when disabled
  • Focusable elements: Targets a[href], button, input, select, textarea, and elements with tabindex (excluding tabindex="-1")

Examples

Dialog with Focus Trap

function Dialog({ open, onClose, children }) {
  const dialogRef = useRef<HTMLDialogElement>(null);
  useFocusTrap(dialogRef, open);

  return (
    <dialog ref={dialogRef} open={open}>
      {children}
      <button onClick={onClose}>OK</button>
      <button onClick={onClose}>Cancel</button>
    </dialog>
  );
}

Drawer Component

function Drawer({ isOpen, onClose }) {
  const drawerRef = useRef<HTMLDivElement>(null);
  useFocusTrap(drawerRef, isOpen);

  return (
    <>
      {isOpen && <div className="overlay" onClick={onClose} />}
      <div ref={drawerRef} className={isOpen ? 'drawer open' : 'drawer'}>
        <nav>
          <a href="/home">Home</a>
          <a href="/about">About</a>
          <a href="/contact">Contact</a>
        </nav>
        <button onClick={onClose}>Close</button>
      </div>
    </>
  );
}

Popover Menu

function PopoverMenu({ trigger }) {
  const [isOpen, setIsOpen] = useState(false);
  const menuRef = useRef<HTMLDivElement>(null);
  useFocusTrap(menuRef, isOpen);

  return (
    <div>
      <button onClick={() => setIsOpen(true)}>{trigger}</button>
      {isOpen && (
        <div ref={menuRef} className="popover">
          <button onClick={() => console.log('Edit')}>Edit</button>
          <button onClick={() => console.log('Delete')}>Delete</button>
          <button onClick={() => setIsOpen(false)}>Cancel</button>
        </div>
      )}
    </div>
  );
}

Accessibility

  • Implements ARIA Authoring Practices Guide pattern for focus management in dialogs and overlays
  • Prevents keyboard users from tabbing out of modal content into background page
  • Automatically restores focus to the trigger element when modal closes
  • Works with screen readers by maintaining logical focus flow

Build docs developers (and LLMs) love