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.
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
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.
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>
);
}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>
);
}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.
When contain={true}:
When restoreFocus={true}:
When autoFocus={true}:
React Aria maintains an internal tree of focus scopes to handle:
Install with Tessl CLI
npx tessl i tessl/npm-react-aria--focus