@davidevmod/focus-trap
TypeScript icon, indicating that this package has built-in type declarations

4.0.1 • Public • Published

@davidevmod/focus-trap

ci codecov npm version license

A tiny and performant library to trap the focus within your DOM elements.

Features

  • Trap the focus within a group of DOM elements
  • Choose an element receiving the initial focus
  • Prevent clicks on elements outside of the trap
  • Demolish a trap after an Esc key press
  • Focus a given element once the trap is demolished
  • Build, demolish, pause and resume your focus trap

Contents

Installation

# Install with
npm add @davidevmod/focus-trap
# or
yarn add @davidevmod/focus-trap
# or
pnpm add @davidevmod/focus-trap

Usage

import { focusTrap } from '@davidevmod/focus-trap'; and call it with an argument of type TrapArg:

import { focusTrap } from '@davidevmod/focus-trap';

const myElement = document.getElementById('myID');

// You can build a focus trap in different ways:

focusTrap(['myID']);

focustrap([myElement]);

focustrap({ roots: ['myID'] });

focustrap({ roots: [myElement] });

// All of the above calls would build the very same trap.

// Pause the trap.
focusTrap('PAUSE');

// Resume the trap.
focusTrap('RESUME');

// Demolish the trap.
focusTrap('DEMOLISH');

Default behaviour

By default, when building a focus trap by providing only an array of roots, this is what happens:

  • The focus is given to the first tabbable element contained in the roots
  • Tab and Shift+Tab keys cycle through the roots' tabbable elements
  • Click events outside of the focus trap are prevented
  • Whenever the Esc key is pressed, the trap is demolished
  • Once the trap is demolished, focus is returned to what was the activeElement at the time the trap was built

API

type Focusable = HTMLElement | SVGElement;

type Roots = (Focusable | string)[];

interface TrapConfig {
  roots: Roots;
  initialFocus?: boolean | Focusable | string;
  returnFocus?: boolean | Focusable | string;
  lock?: boolean;
  escape?: boolean;
}

type TrapAction = 'PAUSE' | 'RESUME' | 'DEMOLISH';

type TrapArg = Roots | TrapConfig | TrapAction;

TrapConfig

You can tweak the behaviour of your trap by calling focusTrap with a TrapConfig object:

Property Required Type Default value
roots Yes (Focusable | string)[] -
initialFocus No boolean | Focusable | string true
returnFocus No boolean | Focusable | string true
lock No boolean true
escape No boolean true
  • roots
    The array of elements (and/or IDs) within which the focus has to be trapped.

  • initialFocus
    The element receiving the focus as soon as the trap is built.
    By default it will be the first tabbable element in the trap.
    You can provide your designated element (or ID) or the boolean false to switch off the default behaviour.

  • returnFocus
    The element that will receive the focus once the trap is demolished.
    By default it will be the element that was the activeElement right before the trap was built.
    You can provide your designated element (or ID) or the boolean false to switch off the default behaviour.

  • lock
    The behavior for clicks outside of the trap.
    By default clicks on elements outside of the trap are prevented.
    You can provide the boolean false to switch off the default behaviour.

    Note
    Only mousedown, touchstart, click and the browser default behavior are prevented.
    So, if you need to, you can make an element outside of the trap clickable even when lock is true, for example, by listening for mouseup events.

  • escape
    The behaviour for Esc key presses.
    By default the trap is demolished Whenever the Esc key is pressed.
    You can provide the boolean false to switch off the default behaviour.

TrapAction

Calling focusTrap with "PAUSE", "RESUME" or "DEMOLISH" will pause, resume or demolish the focus trap.

Return value

A shollow copy of the NormalisedTrapConfig used internally by the library, which is the provided TrapConfig with IDs resolved to actual elements and default values set:

type Focusable = HTMLElement | SVGElement;

interface NormalisedTrapConfig {
  roots: Focusable[];
  initialFocus: boolean | Focusable;
  returnFocus: Focusable | null;
  lock: boolean;
  escape: boolean;
}

Note
The normalised roots are updated at every Tab key press to account for any relevant mutaion (eg, elements attached to or detached from the DOM) so they only represent a snapshot of an ever changing array of elements.

This value is rarely useful, it may be used to eg, implement a stack of focus traps.

Dependencies

The only dependency is true-myth, used simply to liberate funcitons from exceptions (as side effects) by including them in the return value.

It makes the codebase more robust and self-explanatory.

Browser Support

The library can run in any major browser.

Note
The codebase is tested only against Chromium-based browsers. That's because the e2e tests use Cypress, which does not support native browser events (in particular Tab key presses), problem that is solved by using the Cypress Real Events plugin which does allow for native browser events in Cypress, but only in the presence of Chrome Devtools.

Demo

There is a live demo in which you can play around with focus traps to appreciate the way they work: https://focus-trap-demo.vercel.app/

The source code can be found in this repo.

Special thanks ❤️

The logic for the treatement of edge cases, in matter of browser consistency, regarding tab indexes and tabbability (found in tabbability.ts) is took from tabbable.

This small library has been around for many years and, at the time of writing, can boast 180 dependant packages and one million weekly downloads while having zero open issues 😱 which makes feel safe about the reliability of the edge case logic.

The reason why tabbable is not being used as a dependency is that it would be an overkill and @davidevmod/focus-trap aims to be as simple and lightweight as possible.

Also much obliged to the whole focus-trap project in general, which has been a huge point of reference.

🌎 Contributing

Any kind of contribution is more than welcome.
Check out the CONTRIBUTING.md to get started.

Package Sidebar

Install

npm i @davidevmod/focus-trap

Weekly Downloads

3

Version

4.0.1

License

MIT

Unpacked Size

28.7 kB

Total Files

12

Last publish

Collaborators

  • davidevmod