0
# Focus Scope Management
1
2
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.
3
4
## Capabilities
5
6
### FocusScope Component
7
8
A React component that manages focus for its descendants, supporting focus containment, restoration, and auto-focusing.
9
10
```typescript { .api }
11
/**
12
* FocusScope manages focus for its descendants. It supports containing focus inside
13
* the scope, restoring focus to the previously focused element on unmount, and auto
14
* focusing children on mount. It also acts as a container for a programmatic focus
15
* management interface.
16
*/
17
function FocusScope(props: FocusScopeProps): JSX.Element;
18
19
interface FocusScopeProps {
20
/** The contents of the focus scope. */
21
children: ReactNode;
22
23
/**
24
* Whether to contain focus inside the scope, so users cannot
25
* move focus outside, for example in a modal dialog.
26
*/
27
contain?: boolean;
28
29
/**
30
* Whether to restore focus back to the element that was focused
31
* when the focus scope mounted, after the focus scope unmounts.
32
*/
33
restoreFocus?: boolean;
34
35
/** Whether to auto focus the first focusable element in the focus scope on mount. */
36
autoFocus?: boolean;
37
}
38
```
39
40
**Usage Examples:**
41
42
```typescript
43
import React, { useState } from "react";
44
import { FocusScope } from "@react-aria/focus";
45
46
// Modal dialog with focus containment
47
function Modal({ isOpen, onClose, title, children }) {
48
if (!isOpen) return null;
49
50
return (
51
<div className="modal-backdrop" onClick={onClose}>
52
<FocusScope contain restoreFocus autoFocus>
53
<div
54
className="modal-content"
55
onClick={(e) => e.stopPropagation()}
56
role="dialog"
57
aria-modal="true"
58
aria-labelledby="modal-title"
59
>
60
<h2 id="modal-title">{title}</h2>
61
{children}
62
<button onClick={onClose}>Close</button>
63
</div>
64
</FocusScope>
65
</div>
66
);
67
}
68
69
// Popover without focus containment but with restoration
70
function Popover({ isOpen, children }) {
71
if (!isOpen) return null;
72
73
return (
74
<FocusScope restoreFocus>
75
<div className="popover">
76
{children}
77
</div>
78
</FocusScope>
79
);
80
}
81
82
// Auto-focus form
83
function AutoFocusForm({ children }) {
84
return (
85
<FocusScope autoFocus>
86
<form>
87
<input type="text" placeholder="This will be auto-focused" />
88
{children}
89
</form>
90
</FocusScope>
91
);
92
}
93
```
94
95
### useFocusManager Hook
96
97
Returns a FocusManager interface for programmatic focus movement within the nearest FocusScope.
98
99
```typescript { .api }
100
/**
101
* Returns a FocusManager interface for the parent FocusScope.
102
* A FocusManager can be used to programmatically move focus within
103
* a FocusScope, e.g. in response to user events like keyboard navigation.
104
*/
105
function useFocusManager(): FocusManager | undefined;
106
107
interface FocusManager {
108
/** Moves focus to the next focusable or tabbable element in the focus scope. */
109
focusNext(opts?: FocusManagerOptions): FocusableElement | null;
110
/** Moves focus to the previous focusable or tabbable element in the focus scope. */
111
focusPrevious(opts?: FocusManagerOptions): FocusableElement | null;
112
/** Moves focus to the first focusable or tabbable element in the focus scope. */
113
focusFirst(opts?: FocusManagerOptions): FocusableElement | null;
114
/** Moves focus to the last focusable or tabbable element in the focus scope. */
115
focusLast(opts?: FocusManagerOptions): FocusableElement | null;
116
}
117
118
interface FocusManagerOptions {
119
/** The element to start searching from. The currently focused element by default. */
120
from?: Element;
121
/** Whether to only include tabbable elements, or all focusable elements. */
122
tabbable?: boolean;
123
/** Whether focus should wrap around when it reaches the end of the scope. */
124
wrap?: boolean;
125
/** A callback that determines whether the given element is focused. */
126
accept?: (node: Element) => boolean;
127
}
128
```
129
130
**Usage Examples:**
131
132
```typescript
133
import React from "react";
134
import { FocusScope, useFocusManager } from "@react-aria/focus";
135
136
// Arrow key navigation in a list
137
function NavigableList({ items }) {
138
const focusManager = useFocusManager();
139
140
const handleKeyDown = (e: React.KeyboardEvent) => {
141
switch (e.key) {
142
case 'ArrowDown':
143
e.preventDefault();
144
focusManager?.focusNext({ wrap: true });
145
break;
146
case 'ArrowUp':
147
e.preventDefault();
148
focusManager?.focusPrevious({ wrap: true });
149
break;
150
case 'Home':
151
e.preventDefault();
152
focusManager?.focusFirst();
153
break;
154
case 'End':
155
e.preventDefault();
156
focusManager?.focusLast();
157
break;
158
}
159
};
160
161
return (
162
<FocusScope>
163
<div
164
role="listbox"
165
onKeyDown={handleKeyDown}
166
tabIndex={0}
167
>
168
{items.map((item, index) => (
169
<div key={index} role="option" tabIndex={-1}>
170
{item}
171
</div>
172
))}
173
</div>
174
</FocusScope>
175
);
176
}
177
178
// Custom navigation with filtering
179
function FilteredNavigableGrid({ items, isEnabled }) {
180
const focusManager = useFocusManager();
181
182
const focusNextEnabled = () => {
183
focusManager?.focusNext({
184
tabbable: true,
185
wrap: true,
186
accept: (node) => {
187
// Only focus enabled items
188
return !node.hasAttribute('data-disabled');
189
}
190
});
191
};
192
193
return (
194
<FocusScope>
195
<div className="grid" onKeyDown={handleKeyDown}>
196
{items.map((item, index) => (
197
<button
198
key={index}
199
data-disabled={!isEnabled(item) || undefined}
200
disabled={!isEnabled(item)}
201
>
202
{item.name}
203
</button>
204
))}
205
</div>
206
</FocusScope>
207
);
208
}
209
```
210
211
### Element Scope Detection
212
213
Utility for detecting if an element is within a child scope of the currently active scope.
214
215
```typescript { .api }
216
/**
217
* Checks if element is in child of active scope.
218
* @private - Internal utility, may change without notice
219
*/
220
function isElementInChildOfActiveScope(element: Element): boolean;
221
```
222
223
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.
224
225
## Focus Scope Behavior
226
227
### Focus Containment
228
229
When `contain={true}`:
230
- Tab and Shift+Tab navigation is trapped within the scope
231
- Focus cannot escape the scope boundaries via keyboard navigation
232
- If focus moves outside programmatically, it's automatically returned to the scope
233
- Useful for modal dialogs and other overlay interfaces
234
235
### Focus Restoration
236
237
When `restoreFocus={true}`:
238
- The previously focused element is remembered when the scope mounts
239
- Focus is restored to that element when the scope unmounts
240
- Handles cases where the original element is no longer in the DOM
241
- Respects focus scope hierarchy for nested scopes
242
243
### Auto Focus
244
245
When `autoFocus={true}`:
246
- Automatically focuses the first focusable/tabbable element when the scope mounts
247
- Does not move focus if an element within the scope is already focused
248
- Useful for dialogs and forms that should immediately be keyboard accessible
249
250
### Focus Scope Tree
251
252
React Aria maintains an internal tree of focus scopes to handle:
253
- Nested scope behavior and precedence
254
- Proper focus restoration in complex applications
255
- Child scope detection and focus containment rules
256
- Scope activation tracking for multiple overlapping scopes