Skip to main content
Focus trapping allows you to limit hotkeys to specific elements in your DOM. This is useful when you want hotkeys to only work when a particular component is focused, creating a more controlled and predictable user experience.

What is Focus Trapping?

By default, hotkeys listen to the entire document. Focus trapping lets you attach hotkeys to a specific element so they only trigger when that element (or its children) has focus.
When you use a ref with useHotkeys, the hook returns that ref, allowing you to attach it to any React element.

Basic Focus Trapping

The useHotkeys hook returns a ref that you can attach to any element:
import { useHotkeys } from 'react-hotkeys-hook';

function FocusedInput() {
  const ref = useHotkeys<HTMLDivElement>('enter', () => {
    console.log('Enter pressed inside this div');
  });

  return (
    <div ref={ref} tabIndex={0}>
      Click here to focus, then press Enter
    </div>
  );
}
The element must be focusable for focus trapping to work. Add tabIndex={0} to make non-interactive elements focusable.

How Focus Trapping Works

When you attach a ref to an element:
  1. The hotkey only triggers when the element or its children have focus
  2. If focus is outside the element, the hotkey is ignored
  3. The event propagation is stopped if the element is not focused
function Example() {
  const boxRef = useHotkeys<HTMLDivElement>('a', () => {
    console.log('A pressed inside box');
  });

  return (
    <div>
      <div ref={boxRef} tabIndex={0} style={{ border: '2px solid blue' }}>
        <p>Focus this box and press 'A'</p>
        <input type="text" placeholder="Child elements also work" />
      </div>
      <div>
        <p>Press 'A' here - nothing happens</p>
      </div>
    </div>
  );
}

Multiple Focused Elements

You can have multiple elements with different hotkeys:
function MultipleBoxes() {
  const box1Ref = useHotkeys<HTMLDivElement>('1', () => {
    console.log('Box 1 activated');
  });

  const box2Ref = useHotkeys<HTMLDivElement>('2', () => {
    console.log('Box 2 activated');
  });

  const box3Ref = useHotkeys<HTMLDivElement>('3', () => {
    console.log('Box 3 activated');
  });

  return (
    <div>
      <div ref={box1Ref} tabIndex={0} style={{ margin: 10, padding: 20, border: '2px solid red' }}>
        Focus here and press '1'
      </div>
      <div ref={box2Ref} tabIndex={0} style={{ margin: 10, padding: 20, border: '2px solid green' }}>
        Focus here and press '2'
      </div>
      <div ref={box3Ref} tabIndex={0} style={{ margin: 10, padding: 20, border: '2px solid blue' }}>
        Focus here and press '3'
      </div>
    </div>
  );
}

Focus Trapping with Forms

Focus trapping is particularly useful for form components:
function Form() {
  const formRef = useHotkeys<HTMLFormElement>('ctrl+s', (e) => {
    e.preventDefault();
    console.log('Form saved');
  }, { 
    enableOnFormTags: ['input', 'textarea'] 
  });

  return (
    <form ref={formRef}>
      <input type="text" placeholder="Name" />
      <input type="email" placeholder="Email" />
      <textarea placeholder="Message" />
      <button type="submit">Submit</button>
      <p>Focus any form field and press Ctrl+S to save</p>
    </form>
  );
}
Remember to use enableOnFormTags when you want hotkeys to work inside form elements, as they’re disabled by default.

Nested Focus Areas

Focus trapping respects the DOM hierarchy - child elements can have their own hotkeys:
function NestedFocus() {
  const containerRef = useHotkeys<HTMLDivElement>('esc', () => {
    console.log('Escape in container');
  });

  const innerRef = useHotkeys<HTMLDivElement>('enter', () => {
    console.log('Enter in inner box');
  });

  return (
    <div ref={containerRef} tabIndex={0} style={{ padding: 20, border: '2px solid blue' }}>
      <p>Focus here and press ESC</p>
      <div ref={innerRef} tabIndex={0} style={{ padding: 20, border: '2px solid red' }}>
        <p>Focus here and press Enter (ESC also works here)</p>
      </div>
    </div>
  );
}
When the inner box is focused:
  • Its own hotkeys (Enter) work
  • Parent hotkeys (Escape) also work because the inner element is contained within the parent

TypeScript with Refs

Specify the element type as a generic parameter:
// For div elements
const divRef = useHotkeys<HTMLDivElement>('a', callback);

// For input elements
const inputRef = useHotkeys<HTMLInputElement>('enter', callback);

// For button elements
const buttonRef = useHotkeys<HTMLButtonElement>('space', callback);

// For custom components (use the underlying DOM element)
const sectionRef = useHotkeys<HTMLElement>('esc', callback);
A practical example combining focus trapping with modal dialogs:
import { useHotkeys } from 'react-hotkeys-hook';
import { useEffect, useRef } from 'react';

function Modal({ isOpen, onClose }) {
  const modalRef = useHotkeys<HTMLDivElement>('esc', () => {
    onClose();
  });

  // Focus the modal when it opens
  useEffect(() => {
    if (isOpen && modalRef.current) {
      modalRef.current.focus();
    }
  }, [isOpen]);

  if (!isOpen) return null;

  return (
    <div className="modal-overlay">
      <div 
        ref={modalRef} 
        tabIndex={-1} 
        className="modal"
      >
        <h2>Modal Title</h2>
        <p>Press ESC to close (only works when modal is focused)</p>
        <button onClick={onClose}>Close</button>
      </div>
    </div>
  );
}

Editable Content Area

Create an editor with scoped hotkeys:
function CodeEditor() {
  const [code, setCode] = useState('');
  
  const editorRef = useHotkeys<HTMLDivElement>('ctrl+/', () => {
    console.log('Toggle comment');
  }, { enableOnContentEditable: true });

  useHotkeys<HTMLDivElement>('ctrl+d', () => {
    console.log('Duplicate line');
  }, { enableOnContentEditable: true });

  return (
    <div
      ref={editorRef}
      contentEditable
      tabIndex={0}
      onInput={(e) => setCode(e.currentTarget.textContent || '')}
      style={{
        border: '1px solid #ccc',
        padding: '10px',
        minHeight: '200px',
        fontFamily: 'monospace'
      }}
    >
      {code}
    </div>
  );
}

Shadow DOM Support

Focus trapping works with Shadow DOM and Web Components:
function CustomElement() {
  const elementRef = useHotkeys<HTMLDivElement>('ctrl+k', () => {
    console.log('Hotkey in custom element');
  });

  return (
    <custom-component ref={elementRef}>
      <p>Focus this custom element and press Ctrl+K</p>
    </custom-component>
  );
}
The hook correctly checks if the active element is within the shadow root.

Combining with Scopes

You can combine focus trapping with scopes for more complex control:
import { useHotkeys, useHotkeysContext } from 'react-hotkeys-hook';

function ScopedFocusComponent() {
  const { enableScope, disableScope } = useHotkeysContext();
  
  const panelRef = useHotkeys<HTMLDivElement>('enter', () => {
    console.log('Enter pressed in panel');
  }, { scopes: 'panel' });

  return (
    <div 
      ref={panelRef}
      tabIndex={0}
      onFocus={() => enableScope('panel')}
      onBlur={() => disableScope('panel')}
    >
      Focus this panel to enable its hotkeys
    </div>
  );
}

Global vs. Focused Hotkeys

Mix global and focused hotkeys in the same component:
function App() {
  // Global hotkey - works anywhere
  useHotkeys('ctrl+k', () => {
    console.log('Global command palette');
  });

  // Focused hotkey - only in this panel
  const panelRef = useHotkeys<HTMLDivElement>('space', () => {
    console.log('Space in panel');
  });

  return (
    <div>
      <div ref={panelRef} tabIndex={0}>
        Focus here and press Space
      </div>
      <div>
        Ctrl+K works everywhere, Space only works in the panel above
      </div>
    </div>
  );
}

Accessibility Considerations

When using focus trapping, consider accessibility:
  • Make focused elements visually distinct (use :focus styles)
  • Ensure keyboard navigation works intuitively
  • Provide clear visual indicators of which element has focus
  • Test with screen readers
/* Example focus styles */
.focusable:focus {
  outline: 2px solid blue;
  outline-offset: 2px;
  background-color: rgba(0, 0, 255, 0.1);
}

Best Practices

1

Use tabIndex appropriately

Set tabIndex={0} for elements that should be in the natural tab order, or tabIndex={-1} for elements that should only be programmatically focused.
2

Auto-focus when needed

Use useEffect to automatically focus elements when they appear (like modals).
3

Clear visual feedback

Always style :focus states so users know which element is active.
4

Combine with scopes for complex UIs

Use both focus trapping and scopes together for maximum control in complex applications.

Without Focus Trapping

If you don’t use a ref, hotkeys work globally:
// This hotkey works anywhere in the document
useHotkeys('ctrl+s', () => {
  console.log('Save');
});

// This is the same as:
const ref = useHotkeys('ctrl+s', () => {
  console.log('Save');
});
// But not using the ref anywhere

Build docs developers (and LLMs) love