High-precision focus management utility with full composed tree support. Handles complex focus rules including tabindex ordering, radio groups, inert, and shadow DOM.
Note
Supports shadow DOM traversal via the composed tree. Only open shadow roots are included; closed shadow roots are not accessible.
npm i power-focusable// npm
import {
createFocusTrap,
getFocusables,
getNextFocusable,
getPreviousFocusable,
hasFocusable,
inertOutside,
isFocusable,
} from 'power-focusable';
// CDNs
import { ... } 'https://esm.sh/power-focusable'
// or
import { ... } 'https://cdn.jsdelivr.net/npm/power-focusable/dist/index.js';
// or
import { ... } 'https://unpkg.com/power-focusable/dist/index.js';interface PowerFocusableOptions {
anchor?: Element | null; // default: document.activeElement
composed?: boolean; // default: false
filter?: (element: Element) => boolean; // default: () => true
wrap?: boolean; // default: false
}Specifies the starting element.
Used by getNextFocusable and getPreviousFocusable.
If true, traverses the composed tree (including shadow DOM; slower)
Used by getFocusables, getNextFocusable, getPreviousFocusable, and hasFocusable.
Custom filter function for excluding elements from focus traversal. Useful for advanced focus management such as portals, virtual focus regions, temporary exclusions, or custom focus scopes.
The function should return true to include the element, or false to exclude it.
Used by getFocusables, getNextFocusable, getPreviousFocusable, and hasFocusable.
If true, wraps around to the first or last element when reaching the end.
Used by getNextFocusable and getPreviousFocusable.
Creates a keyboard focus trap within the container. Automatically focuses the container itself when possible; otherwise focuses the first available focusable element.
const cleanup = createFocusTrap(container);
// => () => void
//
// container: ElementReturns all focusable elements within the container.
getFocusables(container);
// => Element[]
//
// container (optional): Element (default: <body>)
// Traverses the composed tree (including shadow DOM; slower)
getFocusables(container, { composed: true });
// Uses custom filter function
getFocusables(container, { filter: (element) => !element.matches('[data-skip-focus]') });Returns the next focusable element within the container, starting from document.activeElement.
getNextFocusable(container);
// => Element | null
//
// container (optional): Element (default: <body>)
// Specifies the starting element
getNextFocusable(container, { anchor: document.querySelector('.button') });
// Traverses the composed tree (including shadow DOM; slower)
getNextFocusable(container, { composed: true });
// Uses custom filter function
getNextFocusable(container, { filter: (element) => !element.matches('[data-skip-focus]') });
// Wraps around to the first element when reaching the end
getNextFocusable(container, { wrap: true });Returns the previous focusable element within the container, starting from document.activeElement.
getPreviousFocusable(container);
// => Element | null
//
// container (optional): Element (default: <body>)
// Specifies the starting element
getPreviousFocusable(container, { anchor: document.querySelector('.button') });
// Traverses the composed tree (including shadow DOM; slower)
getPreviousFocusable(container, { composed: true });
// Uses custom filter function
getPreviousFocusable(container, { filter: (element) => !element.matches('[data-skip-focus]') });
//Wraps around to the last element when reaching the end
getPreviousFocusable(container, { wrap: true });Returns whether the container contains at least one focusable element.
hasFocusable(container);
// => boolean
//
// container (optional): Element (default: <body>)
// Traverses the composed tree (including shadow DOM; slower)
hasFocusable(container, { composed: true });
// Uses custom filter function
hasFocusable(container, { filter: (element) => !element.matches('[data-skip-focus]') });Temporarily applies inert to all elements outside the target element. Useful for modals, dialogs, and overlays.
const cleanup = inertOutside(element);
// => () => void
//
// element: ElementReturns whether the given element is focusable.
isFocusable(element);
// => boolean
//
// element: Element