# How to Use the useFocusTrap Hook for Accessibility in Astryx

> Learn how to use the useFocusTrap hook in Astryx to enhance accessibility for keyboard users by trapping focus within modals and popovers.

- Repository: [Meta/astryx](https://github.com/facebook/astryx)
- Tags: how-to-guide
- Published: 2026-07-02

---

**The `useFocusTrap` hook implements the WAI-ARIA dialog focus-trap pattern, ensuring keyboard users cannot tab out of modals or popovers while providing built-in Escape key handling and circular tab navigation.**

The `useFocusTrap` hook is a core accessibility utility in the [facebook/astryx](https://github.com/facebook/astryx) design system that manages keyboard focus within interactive containers. It is essential for building compliant dialogs, popovers, and any overlay that requires users to complete an action before returning to the main content. This guide covers the hook's architecture, integration patterns, and implementation examples based on the source code in [`packages/core/src/hooks/useFocusTrap.ts`](https://github.com/facebook/astryx/blob/main/packages/core/src/hooks/useFocusTrap.ts).

## Core Architecture of useFocusTrap

The hook combines several accessibility mechanisms to create a robust focus management system. According to the Astryx source code, it detects focusable elements, manages initial focus, intercepts keyboard navigation, and handles the Escape key.

### Focusable Element Detection

The hook uses a constant `FOCUSABLE_SELECTOR` to identify interactive elements within a container. This selector targets buttons, links, inputs, selects, textareas, and any element with a `tabindex` not equal to `-1`. The helper function `getFocusableElements(container)` returns all matching nodes as implemented in [useFocusTrap.ts – lines 23‑33](https://github.com/facebook/astryx/blob/main/packages/core/src/hooks/useFocusTrap.ts#L23-L33).

### Initial Focus Management

When a dialog opens, accessibility best practices require moving focus to the first focusable element. The `focusFirstDescendant(container)` utility walks the focusable elements list and calls `.focus()` on the first accepted target. The hook exposes this behavior through the `focusFirst` return value, allowing programmatic control after state changes as shown in [useFocusTrap.ts – lines 48‑58](https://github.com/facebook/astryx/blob/main/packages/core/src/hooks/useFocusTrap.ts#L48-L58).

### Wrap-Around Keyboard Navigation

The hook intercepts Tab and Shift+Tab keydown events to create a circular focus loop. If focus rests on the last element and the user presses Tab, focus moves to the first element. Conversely, Shift+Tab on the first element moves focus to the last. This "wrap-around" behavior prevents focus from escaping to the background content and is handled in the keydown listener at [useFocusTrap.ts – lines 30‑43](https://github.com/facebook/astryx/blob/main/packages/core/src/hooks/useFocusTrap.ts#L30-L44).

### Escape Key Handling

For modal dialogs, the hook accepts an optional `onEscape` callback. When the user presses the Escape key, this callback fires, typically triggering a close action. This implementation follows the WAI-ARIA authoring practices for dialog dismissal and is located at [useFocusTrap.ts – lines 12‑16](https://github.com/facebook/astryx/blob/main/packages/core/src/hooks/useFocusTrap.ts#L12-L16).

### Focus-Out Protection

A capture-phase `focus` listener tracks the last focused element. If keyboard navigation causes focus to leave the container (detected via `isKeyboardNavigationRef`), the hook redirects focus back to the first focusable descendant. If that element is the same as the current focus (indicating a Shift+Tab from the first element), it falls back to the last descendant. Mouse-initiated focus changes are ignored to support light-dismiss patterns. This logic appears in [useFocusTrap.ts – lines 60‑86](https://github.com/facebook/astryx/blob/main/packages/core/src/hooks/useFocusTrap.ts#L60-L86).

## Integration with usePopover

While `useFocusTrap` can be used directly, Astryx recommends higher-level hooks for common patterns. The `usePopover` hook automatically composes `useFocusTrap`, attaching the trap to the popover's content wrapper and wiring the Escape key to `layer.hide`. This integration occurs in [usePopover.tsx – lines 94‑99](https://github.com/facebook/astryx/blob/main/packages/core/src/Popover/usePopover.tsx#L94-L99), where `useFocusTrap` is instantiated with `isActive: layer.isOpen`.

## Implementation Examples

### Basic Modal with Direct useFocusTrap

For custom dialogs that do not use the Popover component, import `useFocusTrap` directly and attach the `containerRef` to the root element. Call `focusFirst` when the modal opens to ensure immediate keyboard accessibility.

```tsx
import {useFocusTrap} from '@astryxdesign/core';
import {useEffect} from 'react';

export function SimpleModal({open, onClose}: {open: boolean; onClose: () => void}) {
  const {containerRef, focusFirst} = useFocusTrap({
    isActive: open,
    onEscape: onClose,
  });

  // Programmatically focus the first element when opening
  useEffect(() => {
    if (open) {
      focusFirst();
    }
  }, [open, focusFirst]);

  if (!open) return null;

  return (
    <div
      role="dialog"
      aria-modal="true"
      ref={containerRef}
      style={{background: 'white', padding: 20, borderRadius: 8}}
    >
      <h2>Modal title</h2>
      <button onClick={onClose}>Close</button>
      <input placeholder="Focus stays inside…" />
    </div>
  );
}

```

This implementation guarantees that Tab cycles between the "Close" button and the input field, and pressing Escape triggers `onClose`.

### Using useFocusTrap via usePopover

For popover-based interfaces, leverage the built-in focus trap by using `usePopover`. The hook automatically manages focus trapping, Escape handling, and screen-reader announcements.

```tsx
import {usePopover} from '@astryxdesign/core';
import {useRef} from 'react';
import {Calendar} from '@astryxdesign/lab';

export function DatePicker() {
  const inputRef = useRef<HTMLInputElement>(null);
  const popover = usePopover({
    onHide: () => inputRef.current?.focus(),
    closeButtonLabel: 'Close calendar',
    dialogLabel: 'Select a date',
  });

  return (
    <>
      <input ref={inputRef} placeholder="Pick a date…" />
      <button
        ref={popover.triggerRef}
        onClick={popover.toggle}
        {...popover.triggerProps}
      >
        Open calendar
      </button>

      {popover.render(<Calendar />)}
    </>
  );
}

```

Here, `usePopover` instantiates `useFocusTrap` internally, providing a hidden close button for screen readers and returning focus to the trigger when the popover closes.

## Summary

- **`useFocusTrap`** is located in [`packages/core/src/hooks/useFocusTrap.ts`](https://github.com/facebook/astryx/blob/main/packages/core/src/hooks/useFocusTrap.ts) and exports a `containerRef` and `focusFirst` helper.
- The hook uses **`FOCUSABLE_SELECTOR`** to identify interactive elements and implements circular Tab navigation to prevent focus from escaping containers.
- **Escape key handling** is provided via the optional `onEscape` callback, commonly used to close modals.
- For most use cases, prefer **`usePopover`** (which composes `useFocusTrap` automatically) rather than implementing the trap manually.
- Always call **`focusFirst`** after opening a dialog to satisfy WCAG focus management requirements.

## Frequently Asked Questions

### What is the useFocusTrap hook in Astryx?

`useFocusTrap` is a React hook in the Astryx design system that implements the WAI-ARIA dialog focus-trap pattern. It ensures keyboard users remain within a modal or popover container until they explicitly dismiss it, handling Tab navigation, Escape keys, and focus restoration.

### How does useFocusTrap handle tab navigation?

The hook intercepts Tab and Shift+Tab keydown events. When focus reaches the last focusable element, pressing Tab moves focus to the first element, creating a circular loop. Conversely, Shift+Tab from the first element moves focus to the last. This prevents users from accidentally tabbing into background content.

### When should I use useFocusTrap vs usePopover?

Use **`usePopover`** for standard popover, tooltip, or dropdown UIs, as it automatically composes `useFocusTrap` along with positioning and trigger management. Use **`useFocusTrap`** directly for custom dialog implementations or non-popover containers that require focus management, such as full-screen modals or wizard step containers.

### How do I focus the first element when a dialog opens?

The hook returns a **`focusFirst`** function that you should call programmatically when the dialog opens. Wrap this call in a `useEffect` that triggers when the open state changes, as shown in the minimal modal example above. This ensures compliance with WCAG 2.1 Level AA focus order requirements.