0
# Visual Focus Indicators
1
2
Visual focus indicators provide keyboard-only focus rings that respect user input modality. Focus rings are shown only when users are navigating with keyboard, not when using mouse, touch, or other input methods.
3
4
## Capabilities
5
6
### FocusRing Component
7
8
A utility component that applies CSS classes when an element has keyboard focus, with intelligent detection of interaction modality.
9
10
```typescript { .api }
11
/**
12
* A utility component that applies a CSS class when an element has keyboard focus.
13
* Focus rings are visible only when the user is interacting with a keyboard,
14
* not with a mouse, touch, or other input methods.
15
*/
16
function FocusRing(props: FocusRingProps): ReactElement;
17
18
interface FocusRingProps {
19
/** Child element to apply CSS classes to. */
20
children: ReactElement;
21
/** CSS class to apply when the element is focused. */
22
focusClass?: string;
23
/** CSS class to apply when the element has keyboard focus. */
24
focusRingClass?: string;
25
/**
26
* Whether to show the focus ring when something
27
* inside the container element has focus (true), or
28
* only if the container itself has focus (false).
29
* @default false
30
*/
31
within?: boolean;
32
/** Whether the element is a text input. */
33
isTextInput?: boolean;
34
/** Whether the element will be auto focused. */
35
autoFocus?: boolean;
36
}
37
```
38
39
**Usage Examples:**
40
41
```typescript
42
import React from "react";
43
import { FocusRing } from "@react-aria/focus";
44
45
// Basic button with focus ring
46
function Button({ children, ...props }) {
47
return (
48
<FocusRing focusRingClass="focus-visible">
49
<button className="btn" {...props}>
50
{children}
51
</button>
52
</FocusRing>
53
);
54
}
55
56
// Input with different focus classes
57
function TextInput({ label, ...props }) {
58
return (
59
<div>
60
<label>{label}</label>
61
<FocusRing
62
focusClass="input-focused"
63
focusRingClass="input-focus-ring"
64
isTextInput
65
>
66
<input type="text" {...props} />
67
</FocusRing>
68
</div>
69
);
70
}
71
72
// Container with focus-within behavior
73
function Card({ children }) {
74
return (
75
<FocusRing
76
focusRingClass="card-focus-ring"
77
within
78
>
79
<div className="card">
80
{children}
81
</div>
82
</FocusRing>
83
);
84
}
85
86
// Auto-focused element
87
function AutoFocusButton({ children, ...props }) {
88
return (
89
<FocusRing focusRingClass="focus-visible" autoFocus>
90
<button {...props}>
91
{children}
92
</button>
93
</FocusRing>
94
);
95
}
96
```
97
98
### useFocusRing Hook
99
100
Determines whether a focus ring should be shown and provides focus state information with modality detection.
101
102
```typescript { .api }
103
/**
104
* Determines whether a focus ring should be shown to indicate keyboard focus.
105
* Focus rings are visible only when the user is interacting with a keyboard,
106
* not with a mouse, touch, or other input methods.
107
*/
108
function useFocusRing(props?: AriaFocusRingProps): FocusRingAria;
109
110
interface AriaFocusRingProps {
111
/**
112
* Whether to show the focus ring when something
113
* inside the container element has focus (true), or
114
* only if the container itself has focus (false).
115
* @default false
116
*/
117
within?: boolean;
118
/** Whether the element is a text input. */
119
isTextInput?: boolean;
120
/** Whether the element will be auto focused. */
121
autoFocus?: boolean;
122
}
123
124
interface FocusRingAria {
125
/** Whether the element is currently focused. */
126
isFocused: boolean;
127
/** Whether keyboard focus should be visible. */
128
isFocusVisible: boolean;
129
/** Props to apply to the container element with the focus ring. */
130
focusProps: DOMAttributes;
131
}
132
```
133
134
**Usage Examples:**
135
136
```typescript
137
import React from "react";
138
import { useFocusRing } from "@react-aria/focus";
139
import clsx from "clsx";
140
141
// Custom button with focus ring logic
142
function CustomButton({ children, className, ...props }) {
143
const { isFocused, isFocusVisible, focusProps } = useFocusRing();
144
145
return (
146
<button
147
{...props}
148
{...focusProps}
149
className={clsx(className, {
150
'is-focused': isFocused,
151
'focus-visible': isFocusVisible
152
})}
153
>
154
{children}
155
</button>
156
);
157
}
158
159
// Input with custom focus ring behavior
160
function CustomInput({ isTextInput = true, ...props }) {
161
const { isFocused, isFocusVisible, focusProps } = useFocusRing({
162
isTextInput
163
});
164
165
return (
166
<input
167
{...props}
168
{...focusProps}
169
data-focused={isFocused}
170
data-focus-visible={isFocusVisible}
171
style={{
172
outline: isFocusVisible ? '2px solid blue' : 'none',
173
borderColor: isFocused ? '#007acc' : '#ccc'
174
}}
175
/>
176
);
177
}
178
179
// Container with focus-within behavior
180
function FocusWithinContainer({ children }) {
181
const { isFocused, isFocusVisible, focusProps } = useFocusRing({
182
within: true
183
});
184
185
return (
186
<div
187
{...focusProps}
188
className={clsx('container', {
189
'has-focus-within': isFocused,
190
'focus-within-visible': isFocusVisible
191
})}
192
>
193
{children}
194
</div>
195
);
196
}
197
198
// Conditional focus ring display
199
function ConditionalFocusRing({ showFocusRing = true, children }) {
200
const { isFocused, isFocusVisible, focusProps } = useFocusRing();
201
202
return (
203
<div
204
{...focusProps}
205
style={{
206
outline: showFocusRing && isFocusVisible ? '2px solid orange' : 'none'
207
}}
208
>
209
Focus state: {isFocused ? 'focused' : 'not focused'}
210
<br />
211
Focus visible: {isFocusVisible ? 'visible' : 'not visible'}
212
{children}
213
</div>
214
);
215
}
216
```
217
218
## Focus Ring Behavior
219
220
### Interaction Modality Detection
221
222
The focus ring system automatically detects user interaction modality:
223
224
- **Keyboard Navigation**: Focus rings are shown when users navigate with Tab, Arrow keys, or other keyboard interactions
225
- **Mouse/Touch Interaction**: Focus rings are hidden when users interact with mouse clicks or touch gestures
226
- **Programmatic Focus**: Focus rings are shown when focus is moved programmatically (e.g., via `focus()` calls)
227
- **Auto Focus**: Elements with `autoFocus={true}` show focus rings by default
228
229
### Text Input Considerations
230
231
When `isTextInput={true}`:
232
- Focus rings follow different visibility rules optimized for text input fields
233
- Accounts for the fact that text inputs often maintain focus during typing
234
- Provides appropriate visual feedback for screen reader users
235
236
### Focus Within Mode
237
238
When `within={true}`:
239
- The focus ring is shown when any descendant element has focus
240
- Useful for card components, form groups, or other containers
241
- Combines focus-within CSS behavior with modality detection
242
243
### CSS Integration
244
245
The focus ring components work with CSS to provide visual feedback:
246
247
```css
248
/* Example CSS for focus rings */
249
.focus-visible {
250
outline: 2px solid #007acc;
251
outline-offset: 2px;
252
}
253
254
.input-focused {
255
border-color: #007acc;
256
}
257
258
.input-focus-ring {
259
box-shadow: 0 0 0 2px rgba(0, 122, 204, 0.3);
260
}
261
262
.card-focus-ring {
263
box-shadow: 0 0 0 2px rgba(0, 122, 204, 0.2);
264
}
265
266
/* Hide default browser focus outlines when using custom focus rings */
267
button:focus,
268
input:focus {
269
outline: none;
270
}
271
```
272
273
### Accessibility Considerations
274
275
- Focus rings are essential for keyboard users and screen reader users
276
- Never completely hide focus indicators - always provide some visual indication
277
- Ensure sufficient color contrast for focus ring colors
278
- Test focus ring visibility in both light and dark themes
279
- Consider users with visual impairments who may need high contrast focus indicators