⚡ Zero dependencies

focus-trap-tiny

Trap keyboard focus inside modals and dialogs using only native browser APIs. No polyfills. No bloat.

npm install focus-trap-tiny click to copy
< 1KB gzipped
0 dependencies
ES2019 target
WCAG compliant

// live demo

Open a modal and press Tab — focus stays trapped inside. Press Esc or click Close to return focus to the trigger. The live focus log below tracks where focus is at all times. Each button shows off a different v1.1 feature:

Open Modal focuses the first field · focus Email jumps straight to the email input (initialFocus) · empty dialog has no focusable children, so focus falls back to the dialog itself (fallbackFocus).

focus → waiting for interaction...

// usage

import createFocusTrap from 'focus-trap-tiny';

const modal = document.getElementById('my-modal');
const trap = createFocusTrap(modal, {
  initialFocus: '#email',    // selector, element, fn, or true/false
  returnFocus: triggerBtn,    // where focus goes on close
  fallbackFocus: modal,       // used when nothing is focusable
  onEscape: () => closeModal(),
});

// When modal opens
trap.activate();

// When modal closes
trap.deactivate();

// If DOM changes dynamically
trap.update();

// features

⌨️

Tab & Shift+Tab

Full bidirectional keyboard cycling through all focusable elements.

🔙

Configurable focus

Target initial & return focus by selector, element, or function — restores the trigger by default.

🛡️

Escape guard

Catches focus that slips out via mouse click or programmatic focus(), not just Tab.

🪂

Fallback focus

Empty dialog? Focus falls back to the container so it's never lost outside the trap.

🙈

Inert-aware

Skips elements inside [inert] containers and hidden elements.

🔄

Dynamic content

Call trap.update() after DOM changes to re-scan focusable elements.

🚪

Escape callback

Optional onEscape hook to wire up your close logic.

📦

Tree-shakeable

Pure ESM with zero side-effects. Only ships what you import.