CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-react-aria--focus

React hooks and components for accessible focus management including FocusScope for focus containment, FocusRing for visual focus indicators, and utilities for focus navigation and virtual focus handling.

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

focus-scope.mddocs/

Focus Scope Management

Focus scope management provides focus containment, restoration, and programmatic navigation for modal dialogs, popovers, and other overlay interfaces that need to trap focus within a specific area.

Capabilities

FocusScope Component

A React component that manages focus for its descendants, supporting focus containment, restoration, and auto-focusing.

/**
 * FocusScope manages focus for its descendants. It supports containing focus inside
 * the scope, restoring focus to the previously focused element on unmount, and auto
 * focusing children on mount. It also acts as a container for a programmatic focus
 * management interface.
 */
function FocusScope(props: FocusScopeProps): JSX.Element;

interface FocusScopeProps {
  /** The contents of the focus scope. */
  children: ReactNode;
  
  /**
   * Whether to contain focus inside the scope, so users cannot
   * move focus outside, for example in a modal dialog.
   */
  contain?: boolean;
  
  /**
   * Whether to restore focus back to the element that was focused
   * when the focus scope mounted, after the focus scope unmounts.
   */
  restoreFocus?: boolean;
  
  /** Whether to auto focus the first focusable element in the focus scope on mount. */
  autoFocus?: boolean;
}

Usage Examples:

import React, { useState } from "react";
import { FocusScope } from "@react-aria/focus";

// Modal dialog with focus containment
function Modal({ isOpen, onClose, title, children }) {
  if (!isOpen) return null;
  
  return (
    <div className="modal-backdrop" onClick={onClose}>
      <FocusScope contain restoreFocus autoFocus>
        <div 
          className="modal-content"
          onClick={(e) => e.stopPropagation()}
          role="dialog"
          aria-modal="true"
          aria-labelledby="modal-title"
        >
          <h2 id="modal-title">{title}</h2>
          {children}
          <button onClick={onClose}>Close</button>
        </div>
      </FocusScope>
    </div>
  );
}

// Popover without focus containment but with restoration
function Popover({ isOpen, children }) {
  if (!isOpen) return null;
  
  return (
    <FocusScope restoreFocus>
      <div className="popover">
        {children}
      </div>
    </FocusScope>
  );
}

// Auto-focus form
function AutoFocusForm({ children }) {
  return (
    <FocusScope autoFocus>
      <form>
        <input type="text" placeholder="This will be auto-focused" />
        {children}
      </form>
    </FocusScope>
  );
}

useFocusManager Hook

Returns a FocusManager interface for programmatic focus movement within the nearest FocusScope.

/**
 * Returns a FocusManager interface for the parent FocusScope.
 * A FocusManager can be used to programmatically move focus within
 * a FocusScope, e.g. in response to user events like keyboard navigation.
 */
function useFocusManager(): FocusManager | undefined;

interface FocusManager {
  /** Moves focus to the next focusable or tabbable element in the focus scope. */
  focusNext(opts?: FocusManagerOptions): FocusableElement | null;
  /** Moves focus to the previous focusable or tabbable element in the focus scope. */
  focusPrevious(opts?: FocusManagerOptions): FocusableElement | null;
  /** Moves focus to the first focusable or tabbable element in the focus scope. */
  focusFirst(opts?: FocusManagerOptions): FocusableElement | null;
  /** Moves focus to the last focusable or tabbable element in the focus scope. */
  focusLast(opts?: FocusManagerOptions): FocusableElement | null;
}

interface FocusManagerOptions {
  /** The element to start searching from. The currently focused element by default. */
  from?: Element;
  /** Whether to only include tabbable elements, or all focusable elements. */
  tabbable?: boolean;
  /** Whether focus should wrap around when it reaches the end of the scope. */
  wrap?: boolean;
  /** A callback that determines whether the given element is focused. */
  accept?: (node: Element) => boolean;
}

Usage Examples:

import React from "react";
import { FocusScope, useFocusManager } from "@react-aria/focus";

// Arrow key navigation in a list
function NavigableList({ items }) {
  const focusManager = useFocusManager();
  
  const handleKeyDown = (e: React.KeyboardEvent) => {
    switch (e.key) {
      case 'ArrowDown':
        e.preventDefault();
        focusManager?.focusNext({ wrap: true });
        break;
      case 'ArrowUp':
        e.preventDefault();
        focusManager?.focusPrevious({ wrap: true });
        break;
      case 'Home':
        e.preventDefault();
        focusManager?.focusFirst();
        break;
      case 'End':
        e.preventDefault();
        focusManager?.focusLast();
        break;
    }
  };
  
  return (
    <FocusScope>
      <div 
        role="listbox"
        onKeyDown={handleKeyDown}
        tabIndex={0}
      >
        {items.map((item, index) => (
          <div key={index} role="option" tabIndex={-1}>
            {item}
          </div>
        ))}
      </div>
    </FocusScope>
  );
}

// Custom navigation with filtering
function FilteredNavigableGrid({ items, isEnabled }) {
  const focusManager = useFocusManager();
  
  const focusNextEnabled = () => {
    focusManager?.focusNext({
      tabbable: true,
      wrap: true,
      accept: (node) => {
        // Only focus enabled items
        return !node.hasAttribute('data-disabled');
      }
    });
  };
  
  return (
    <FocusScope>
      <div className="grid" onKeyDown={handleKeyDown}>
        {items.map((item, index) => (
          <button
            key={index}
            data-disabled={!isEnabled(item) || undefined}
            disabled={!isEnabled(item)}
          >
            {item.name}
          </button>
        ))}
      </div>
    </FocusScope>
  );
}

Element Scope Detection

Utility for detecting if an element is within a child scope of the currently active scope.

/**
 * Checks if element is in child of active scope.
 * @private - Internal utility, may change without notice
 */
function isElementInChildOfActiveScope(element: Element): boolean;

This function is primarily used internally by React Aria components but is exported for advanced use cases where you need to detect focus scope containment.

Focus Scope Behavior

Focus Containment

When contain={true}:

  • Tab and Shift+Tab navigation is trapped within the scope
  • Focus cannot escape the scope boundaries via keyboard navigation
  • If focus moves outside programmatically, it's automatically returned to the scope
  • Useful for modal dialogs and other overlay interfaces

Focus Restoration

When restoreFocus={true}:

  • The previously focused element is remembered when the scope mounts
  • Focus is restored to that element when the scope unmounts
  • Handles cases where the original element is no longer in the DOM
  • Respects focus scope hierarchy for nested scopes

Auto Focus

When autoFocus={true}:

  • Automatically focuses the first focusable/tabbable element when the scope mounts
  • Does not move focus if an element within the scope is already focused
  • Useful for dialogs and forms that should immediately be keyboard accessible

Focus Scope Tree

React Aria maintains an internal tree of focus scopes to handle:

  • Nested scope behavior and precedence
  • Proper focus restoration in complex applications
  • Child scope detection and focus containment rules
  • Scope activation tracking for multiple overlapping scopes

Install with Tessl CLI

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

docs

focus-navigation.md

focus-ring.md

focus-scope.md

index.md

virtual-focus.md

tile.json