Skip to content

API Overview

SymbolPurposeExecution modeCommon gotcha
createListNavigation()Build keyboard navigation for composite widgetsSyncDisabled items require an explicit predicate
restoreFocus()Restore focus to a target or fallbackSyncReturns false when neither target can receive focus
captureFocus()Capture active focus for one later restorationSyncThe returned function is one-shot

Package Entry Point

ImportPurpose
@vielzeug/focusList navigation and focus restoration primitives.

Core Functions

createListNavigation()

ts
function createListNavigation<T>(options: ListNavigationOptions<T>): ListNavigation<T>;

Creates a keyboard navigation controller with an internal active index.

ParameterTypeDescription
optionsListNavigationOptions<T>Item lookup, key mapping, navigation, typeahead, and lifecycle options.

Returns: ListNavigation<T>.

Example

ts
import { createListNavigation } from '@vielzeug/focus';

const nav = createListNavigation({
  getItems: () => rows,
  isItemDisabled: (item) => item.matches('[aria-disabled="true"]'),
  onNavigate: ({ item }) => item.focus(),
});
MemberReturnContract
handleKeydown(event)booleanHandles configured navigation keys and optional typeahead.
navigate(action)numberMoves programmatically and returns the active index, or -1.
set(index)numberSets the active index when usable, or resets it to -1.
reset()voidClears the active index and typeahead sequence.
getIndex()numberReturns the current usable index, or -1.
getActiveItem()T | undefinedReturns the item at the current usable index.
dispose()voidPermanently disables the controller and aborts disposalSignal.
disposedbooleanIndicates whether the controller is permanently disabled.
disposalSignalAbortSignalAborts when the controller is disposed.
[Symbol.dispose]()voidCalls dispose().

restoreFocus()

ts
function restoreFocus(target: FocusTarget, options?: RestoreFocusOptions): boolean;

Attempts to focus a connected target that is neither disabled nor inert.

ParameterTypeDescription
targetFocusTargetElement or getter resolved when restoreFocus() is called.
optionsRestoreFocusOptionsOptional lazy fallback and preventScroll flag.

Returns: booleantrue when focus moved to the target or fallback.

Example

ts
import { restoreFocus } from '@vielzeug/focus';

restoreFocus(() => triggerElement, {
  fallback: () => document.body,
  preventScroll: true,
});

captureFocus()

ts
function captureFocus(options?: CaptureFocusOptions): FocusRestorer;

Captures the deepest active element immediately and returns a one-shot restoration function.

ParameterTypeDescription
optionsCaptureFocusOptionsOptional lazy fallback, preventScroll, and cancellation signal.

Returns: FocusRestorer. Its first call attempts restoration; later calls return false.

Example

ts
import { captureFocus } from '@vielzeug/focus';

const restore = captureFocus({ fallback: () => document.body });

dialog.showModal();
dialog.addEventListener('close', restore, { once: true });

Types

ts
type MaybeGetter<T> = T | (() => T);

type ListNavigationAction = 'first' | 'last' | 'next' | 'prev';
type ListKeyAction = ListNavigationAction | 'typeahead';

type ListNavigationChange<T> = {
  action: ListKeyAction;
  event?: KeyboardEvent;
  index: number;
  item: T;
};

type ListNavigationTypeaheadOptions<T> = {
  delayMs?: number;
  getLabel: (item: T, index: number) => string;
};

type ListNavigationOptions<T> = {
  direction?: MaybeGetter<'ltr' | 'rtl'>;
  disabled?: MaybeGetter<boolean | undefined>;
  getItems: () => readonly T[];
  isItemDisabled?: (item: T, index: number) => boolean;
  keys?: Partial<Record<ListNavigationAction, readonly string[]>>;
  loop?: boolean;
  onNavigate?: (change: ListNavigationChange<T>) => void;
  orientation?: MaybeGetter<'both' | 'horizontal' | 'vertical'>;
  signal?: AbortSignal;
  typeahead?: ListNavigationTypeaheadOptions<T>;
};

type ListNavigation<T> = {
  readonly disposalSignal: AbortSignal;
  readonly disposed: boolean;
  dispose(): void;
  getActiveItem(): T | undefined;
  getIndex(): number;
  handleKeydown(event: KeyboardEvent): boolean;
  navigate(action: ListNavigationAction): number;
  reset(): void;
  set(index: number): number;
  [Symbol.dispose](): void;
};

type FocusTarget = HTMLElement | SVGElement | null | undefined | (() => HTMLElement | SVGElement | null | undefined);

type RestoreFocusOptions = {
  fallback?: FocusTarget;
  preventScroll?: boolean;
};

type CaptureFocusOptions = RestoreFocusOptions & {
  signal?: AbortSignal;
};

type FocusRestorer = () => boolean;

typeahead.delayMs defaults to 500. Non-finite or non-positive values use the default.

Errors

@vielzeug/focus does not export custom error classes.