CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-react-focus-lock

React focus management library that provides robust focus trapping functionality for modal dialogs and accessibility compliance

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

focus-control-hooks.mddocs/

Focus Control Hooks

React hooks for programmatic focus management, providing fine-grained control over focus behavior within and outside of focus locks. These hooks enable advanced focus manipulation scenarios and focus state tracking.

Capabilities

useFocusScope Hook

Returns FocusControl over the current FocusLock. Can only be used within a FocusLock component.

/**
 * Returns FocusControl over the current FocusLock
 * Can only be used within FocusLock component
 * @returns FocusControl interface for managing focus
 */
function useFocusScope(): FocusControl;

interface FocusControl {
  /** Move focus to the current scope, acts as autofocus */
  autoFocus(): Promise<void>;
  
  /** Focus the next element in the scope */
  focusNext(options?: FocusOptions): Promise<void>;
  
  /** Focus the previous element in the scope */
  focusPrev(options?: FocusOptions): Promise<void>;
  
  /** Focus the first element in the scope */
  focusFirst(options?: Pick<FocusOptions, 'onlyTabbable'>): Promise<void>;
  
  /** Focus the last element in the scope */
  focusLast(options?: Pick<FocusOptions, 'onlyTabbable'>): Promise<void>;
}

interface FocusOptions {
  /** Enable focus cycle, default: true */
  cycle?: boolean;
  
  /** Limit to tabbable elements only, default: true */
  onlyTabbable?: boolean;
}

Usage Example:

import React from "react";
import FocusLock, { useFocusScope } from "react-focus-lock";

function NavigableModal() {
  const focusScope = useFocusScope();

  const handleKeyDown = async (event: React.KeyboardEvent) => {
    switch (event.key) {
      case 'ArrowDown':
        event.preventDefault();
        await focusScope.focusNext();
        break;
      case 'ArrowUp':
        event.preventDefault();
        await focusScope.focusPrev();
        break;
      case 'Home':
        event.preventDefault();
        await focusScope.focusFirst();
        break;
      case 'End':
        event.preventDefault();
        await focusScope.focusLast();
        break;
    }
  };

  return (
    <div onKeyDown={handleKeyDown}>
      <input placeholder="First input" />
      <button>Middle button</button>
      <input placeholder="Last input" />
    </div>
  );
}

function App() {
  return (
    <FocusLock>
      <NavigableModal />
    </FocusLock>
  );
}

useFocusController Hook

Returns FocusControl over given elements. Can be used outside of FocusLock and accepts HTML elements or React refs.

/**
 * Returns FocusControl over given elements
 * Can be used outside of FocusLock
 * @param shards - HTML elements or React refs to control
 * @returns FocusControl interface for managing focus
 */
function useFocusController<Elements extends HTMLElement = HTMLElement>(
  ...shards: ReadonlyArray<HTMLElement | { current: HTMLElement | null }>
): FocusControl;

Usage Example:

import React, { useRef } from "react";
import { useFocusController } from "react-focus-lock";

function CustomFocusController() {
  const firstRef = useRef<HTMLInputElement>(null);
  const secondRef = useRef<HTMLButtonElement>(null);
  const thirdRef = useRef<HTMLInputElement>(null);
  
  const focusController = useFocusController(firstRef, secondRef, thirdRef);

  const handleNext = async () => {
    await focusController.focusNext();
  };

  const handlePrevious = async () => {
    await focusController.focusPrev();
  };

  const handleAutoFocus = async () => {
    await focusController.autoFocus();
  };

  return (
    <div>
      <input ref={firstRef} placeholder="First controlled element" />
      <button ref={secondRef}>Second controlled element</button>
      <input ref={thirdRef} placeholder="Third controlled element" />
      
      <div>
        <button onClick={handleAutoFocus}>Auto Focus</button>
        <button onClick={handleNext}>Focus Next</button>
        <button onClick={handlePrevious}>Focus Previous</button>
      </div>
    </div>
  );
}

useFocusState Hook

Returns focus state information for a given node, tracking whether the node or its children have focus.

/**
 * Returns focus state information for a given node
 * @param callbacks - Optional focus/blur callbacks
 * @returns Object with focus state and handlers
 */
function useFocusState<T extends Element>(callbacks?: FocusCallbacks): {
  /** Whether currently focused or focus is inside */
  active: boolean;
  
  /** Focus state string indicating type of focus relationship */
  state: string;
  
  /** Focus handler to be passed to the node */
  onFocus: FocusEventHandler<T>;
  
  /** Reference to the node */
  ref: RefObject<T>;
};

interface FocusCallbacks {
  /** Called when focus enters the node */
  onFocus(): void;
  
  /** Called when focus leaves the node */
  onBlur(): void;
}

Usage Example:

import React from "react";
import { useFocusState } from "react-focus-lock";

function FocusTracker() {
  const { active, state, ref, onFocus } = useFocusState<HTMLDivElement>({
    onFocus: () => console.log("Focus entered"),
    onBlur: () => console.log("Focus left")
  });

  return (
    <div ref={ref} onFocus={onFocus} className={active ? "focused" : "not-focused"}>
      <h3>Focus Status: {active ? "Focused" : "Not Focused"}</h3>
      <p>Focus State: {state}</p>
      <input placeholder="Input inside tracker" />
      <button>Button inside tracker</button>
    </div>
  );
}

useFocusInside Hook

Moves focus inside a given node reference.

/**
 * Moves focus inside a given node reference
 * @param node - React ref pointing to the target node
 */
function useFocusInside(node: RefObject<HTMLElement>): void;

Usage Example:

import React, { useRef, useEffect } from "react";
import { useFocusInside } from "react-focus-lock";

function AutoFocusContainer() {
  const containerRef = useRef<HTMLDivElement>(null);
  
  // This will move focus inside the container on mount
  useFocusInside(containerRef);

  return (
    <div ref={containerRef}>
      <h3>Focus will move here</h3>
      <input placeholder="This might get focused" />
      <button>Or this button</button>
    </div>
  );
}

function ConditionalFocus({ shouldFocus }: { shouldFocus: boolean }) {
  const containerRef = useRef<HTMLDivElement>(null);
  
  useEffect(() => {
    if (shouldFocus && containerRef.current) {
      // Manually trigger focus inside
      useFocusInside(containerRef);
    }
  }, [shouldFocus]);

  return (
    <div ref={containerRef}>
      <input placeholder="Conditional focus target" />
      <button>Another target</button>
    </div>
  );
}

Install with Tessl CLI

npx tessl i tessl/npm-react-focus-lock

docs

focus-control-hooks.md

focus-lock-components.md

index.md

typescript-types.md

tile.json