CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-focus-trap

Trap focus within a DOM node for accessible modals and interactive UI components.

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

Focus Trap

Focus Trap is a vanilla JavaScript library that traps focus within a DOM node, essential for building accessible modals, menus, and interactive UI components. It handles Tab and Shift+Tab navigation cycles within designated containers, blocks clicks outside the trap, supports Escape key deactivation, and automatically restores focus to the previously focused element when deactivated.

Package Information

  • Package Name: focus-trap
  • Package Type: npm
  • Language: JavaScript/TypeScript
  • Installation: npm install focus-trap

Core Imports

import { createFocusTrap } from 'focus-trap';

For CommonJS:

const { createFocusTrap } = require('focus-trap');

For UMD (browser):

<script src="https://unpkg.com/tabbable/dist/index.umd.js"></script>
<script src="https://unpkg.com/focus-trap/dist/focus-trap.umd.js"></script>
<script>
  // Available as global: focusTrap.createFocusTrap
</script>

Basic Usage

import { createFocusTrap } from 'focus-trap';

// Create a focus trap on a modal element
const container = document.getElementById('modal');
const focusTrap = createFocusTrap('#modal', {
  onActivate: () => container.classList.add('is-active'),
  onDeactivate: () => container.classList.remove('is-active'),
});

// Activate the trap when modal opens
document.getElementById('open-modal').addEventListener('click', () => {
  focusTrap.activate();
});

// Deactivate when modal closes
document.getElementById('close-modal').addEventListener('click', () => {
  focusTrap.deactivate();
});

Architecture

Focus Trap is built around several key components:

  • Trap Creation: createFocusTrap() function creates trap instances for specific containers
  • Focus Management: Automatic focus cycling within trap boundaries using Tab/Shift+Tab keys
  • Event Handling: Mouse, keyboard, and focus event interception to maintain trap boundaries
  • Stack Management: Coordination between multiple active traps with automatic pausing/unpausing
  • Tabbable Integration: Uses the 'tabbable' library to determine focusable and tabbable elements
  • Shadow DOM Support: Compatible with Shadow DOM elements and custom focus behavior

Capabilities

Trap Creation and Management

Core functionality for creating and managing focus traps on DOM elements.

function createFocusTrap(
  elements: HTMLElement | SVGElement | string | Array<HTMLElement | SVGElement | string>,
  userOptions?: Options
): FocusTrap;

Trap Creation

Trap Control Methods

Methods for activating, deactivating, pausing, and updating focus traps.

interface FocusTrap {
  readonly active: boolean;
  readonly paused: boolean;
  activate(activateOptions?: ActivateOptions): FocusTrap;
  deactivate(deactivateOptions?: DeactivateOptions): FocusTrap;
  pause(pauseOptions?: PauseOptions): FocusTrap;
  unpause(unpauseOptions?: UnpauseOptions): FocusTrap;
  updateContainerElements(
    containerElements: HTMLElement | SVGElement | string | Array<HTMLElement | SVGElement | string>
  ): FocusTrap;
}

Trap Control

Configuration Options

Extensive configuration system for customizing trap behavior, focus targets, and event handling.

interface Options {
  // Lifecycle callbacks
  onActivate?: () => void;
  onPostActivate?: () => void;
  onDeactivate?: () => void;
  onPostDeactivate?: () => void;
  
  // Focus management
  initialFocus?: FocusTargetOrFalse | undefined | (() => void);
  fallbackFocus?: FocusTarget;
  returnFocusOnDeactivate?: boolean;
  setReturnFocus?: FocusTargetValueOrFalse | ((previousActiveElement: HTMLElement | SVGElement) => FocusTargetValueOrFalse);
  
  // User interaction
  escapeDeactivates?: boolean | ((event: KeyboardEvent) => boolean);
  clickOutsideDeactivates?: boolean | ((event: MouseEvent | TouchEvent) => boolean);
  allowOutsideClick?: boolean | ((event: MouseEvent | TouchEvent) => boolean);
  
  // Other options
  preventScroll?: boolean;
  delayInitialFocus?: boolean;
  document?: Document;
  tabbableOptions?: FocusTrapTabbableOptions;
  trapStack?: Array<FocusTrap>;
  isKeyForward?: (event: KeyboardEvent) => boolean;
  isKeyBackward?: (event: KeyboardEvent) => boolean;
}

Configuration

Tabbable Integration

Focus-trap relies on the tabbable library to identify focusable and tabbable elements within containers.

// Functions from tabbable used internally by focus-trap
import {
  tabbable,     // Gets tabbable elements in tab order
  focusable,    // Gets all focusable elements
  isFocusable,  // Checks if single element is focusable
  isTabbable,   // Checks if single element is tabbable
  getTabIndex   // Gets computed tab index value
} from 'tabbable';

The tabbableOptions configuration passes through to these tabbable functions to control:

  • Display checking: How strictly to verify element visibility
  • Shadow DOM: How to traverse shadow DOM boundaries
  • Container inclusion: Whether container elements can receive focus

Error Handling

Focus-trap throws errors in specific scenarios that should be handled:

// Common error conditions
type FocusTrapError = 
  | 'NO_TABBABLE_ELEMENTS'     // No focusable elements found and no fallbackFocus
  | 'INVALID_SELECTOR'         // CSS selector doesn't match any elements
  | 'POSITIVE_TABINDEX'        // Positive tabindex in multi-container setup
  | 'CONTAINER_NOT_FOUND';     // Container element no longer in DOM

// Error handling pattern
try {
  const trap = createFocusTrap('#container');
  trap.activate();
} catch (error) {
  if (error.message.includes('at least one tabbable element')) {
    // Handle missing focusable elements
    console.log('Add focusable elements or use fallbackFocus option');
  }
}

Core Types

type FocusTargetValue = HTMLElement | SVGElement | string;
type FocusTargetValueOrFalse = FocusTargetValue | false;
type FocusTarget = FocusTargetValue | (() => FocusTargetValue);
type FocusTargetOrFalse = FocusTargetValueOrFalse | (() => FocusTargetValueOrFalse);

interface FocusTrapTabbableOptions {
  /** How to check if an element is displayed (affects performance) */
  displayCheck?: 'full' | 'legacy-full' | 'non-zero-area' | 'none';
  /** Function to get shadow root for shadow DOM elements */
  getShadowRoot?: (node: Element) => ShadowRoot | boolean;
  /** Whether to include the container element itself as tabbable */
  includeContainer?: boolean;
}

type MouseEventToBoolean = (event: MouseEvent | TouchEvent) => boolean;
type KeyboardEventToBoolean = (event: KeyboardEvent) => boolean;

// Activation/Deactivation/Pause option types
interface ActivateOptions {
  onActivate?: () => void;
  onPostActivate?: () => void;
  checkCanFocusTrap?: (containers: Array<HTMLElement | SVGElement>) => Promise<void>;
}

interface DeactivateOptions {
  returnFocus?: boolean;
  onDeactivate?: () => void;
  onPostDeactivate?: () => void;
  checkCanReturnFocus?: (trigger: HTMLElement | SVGElement) => Promise<void>;
}

interface PauseOptions {
  onPause?: () => void;
  onPostPause?: () => void;
}

interface UnpauseOptions {
  onUnpause?: () => void;
  onPostUnpause?: () => void;
}
Workspace
tessl
Visibility
Public
Created
Last updated
Describes
npmpkg:npm/focus-trap@7.6.x
Publish Source
CLI
Badge
tessl/npm-focus-trap badge