0
# @react-aria/radio
1
2
@react-aria/radio provides React hooks for building accessible radio buttons and radio groups with full WAI-ARIA compliance. It implements the Radio Group pattern with complete keyboard navigation, screen reader support, and mouse/touch interactions while remaining completely unstyled to allow custom styling.
3
4
## Package Information
5
6
- **Package Name**: @react-aria/radio
7
- **Package Type**: npm
8
- **Language**: TypeScript
9
- **Installation**: `npm install @react-aria/radio`
10
11
## Core Imports
12
13
```typescript
14
import { useRadio, useRadioGroup } from "@react-aria/radio";
15
import type { AriaRadioProps, AriaRadioGroupProps, RadioAria, RadioGroupAria, Orientation } from "@react-aria/radio";
16
```
17
18
For CommonJS:
19
20
```javascript
21
const { useRadio, useRadioGroup } = require("@react-aria/radio");
22
```
23
24
## Basic Usage
25
26
```typescript
27
import { useRadio, useRadioGroup } from "@react-aria/radio";
28
import { useRadioGroupState } from "@react-stately/radio"; // Required peer dependency
29
import { useRef } from "react";
30
31
// Radio Group Component
32
function MyRadioGroup(props) {
33
const state = useRadioGroupState(props);
34
const { radioGroupProps, labelProps } = useRadioGroup(props, state);
35
36
return (
37
<div {...radioGroupProps}>
38
<span {...labelProps}>{props.label}</span>
39
{props.children}
40
</div>
41
);
42
}
43
44
// Individual Radio Component
45
function MyRadio(props) {
46
const ref = useRef();
47
const state = useRadioGroupState(props.groupProps);
48
const { inputProps, labelProps, isSelected, isDisabled } = useRadio(props, state, ref);
49
50
return (
51
<label {...labelProps}>
52
<input {...inputProps} ref={ref} />
53
{props.children}
54
</label>
55
);
56
}
57
58
// Usage
59
<MyRadioGroup label="Favorite pet" value={selectedPet} onChange={setSelectedPet}>
60
<MyRadio value="dogs">Dogs</MyRadio>
61
<MyRadio value="cats">Cats</MyRadio>
62
<MyRadio value="birds">Birds</MyRadio>
63
</MyRadioGroup>
64
```
65
66
## Architecture
67
68
@react-aria/radio is built around the separation of behavior and presentation:
69
70
- **Behavior Layer**: The hooks provide all accessibility, keyboard navigation, focus management, and interaction logic
71
- **State Management**: Integrates with @react-stately/radio for radio group state management
72
- **Unstyled Approach**: No CSS or styling is provided, allowing complete visual customization
73
- **ARIA Compliance**: Full implementation of WAI-ARIA Radio Group pattern
74
- **Form Integration**: Native HTML form support with validation
75
76
## Capabilities
77
78
### Radio Group Management
79
80
Provides behavior and accessibility implementation for radio group components that allow users to select a single item from mutually exclusive options.
81
82
```typescript { .api }
83
/**
84
* Provides the behavior and accessibility implementation for a radio group component.
85
* @param props - Props for the radio group
86
* @param state - State for the radio group, as returned by useRadioGroupState
87
* @returns RadioGroupAria object with props and validation state
88
*/
89
function useRadioGroup(props: AriaRadioGroupProps, state: RadioGroupState): RadioGroupAria;
90
91
interface AriaRadioGroupProps extends RadioGroupProps, InputDOMProps, DOMProps, AriaLabelingProps, AriaValidationProps {
92
/** The axis the radio buttons should align with */
93
orientation?: Orientation;
94
/** The name of the radio group for form submission */
95
name?: string;
96
/** Whether the radio group is disabled */
97
isDisabled?: boolean;
98
/** Whether the radio group is read-only */
99
isReadOnly?: boolean;
100
/** Whether the radio group is required */
101
isRequired?: boolean;
102
/** Validation behavior for the radio group */
103
validationBehavior?: 'aria' | 'native';
104
/** Handler called when the radio group receives focus */
105
onFocus?: (e: FocusEvent) => void;
106
/** Handler called when the radio group loses focus */
107
onBlur?: (e: FocusEvent) => void;
108
/** Handler called when the focus changes within the radio group */
109
onFocusChange?: (isFocused: boolean) => void;
110
}
111
112
interface RadioGroupAria extends ValidationResult {
113
/** Props for the radio group wrapper element */
114
radioGroupProps: DOMAttributes;
115
/** Props for the radio group's visible label (if any) */
116
labelProps: DOMAttributes;
117
/** Props for the radio group description element, if any */
118
descriptionProps: DOMAttributes;
119
/** Props for the radio group error message element, if any */
120
errorMessageProps: DOMAttributes;
121
}
122
```
123
124
**Usage Example:**
125
126
```typescript
127
import { useRadioGroup } from "@react-aria/radio";
128
import { useRadioGroupState } from "@react-stately/radio";
129
130
function RadioGroup({ label, children, ...props }) {
131
const state = useRadioGroupState(props);
132
const {
133
radioGroupProps,
134
labelProps,
135
descriptionProps,
136
errorMessageProps,
137
isInvalid,
138
validationErrors
139
} = useRadioGroup(props, state);
140
141
return (
142
<div {...radioGroupProps}>
143
<span {...labelProps}>{label}</span>
144
{props.description && <div {...descriptionProps}>{props.description}</div>}
145
{children}
146
{isInvalid && <div {...errorMessageProps}>{validationErrors.join(' ')}</div>}
147
</div>
148
);
149
}
150
```
151
152
### Individual Radio Button Management
153
154
Provides behavior and accessibility implementation for individual radio buttons within a radio group.
155
156
```typescript { .api }
157
/**
158
* Provides the behavior and accessibility implementation for an individual radio button.
159
* @param props - Props for the radio
160
* @param state - State for the radio group, as returned by useRadioGroupState
161
* @param ref - Ref to the HTML input element
162
* @returns RadioAria object with props and state
163
*/
164
function useRadio(props: AriaRadioProps, state: RadioGroupState, ref: RefObject<HTMLInputElement | null>): RadioAria;
165
166
interface AriaRadioProps extends RadioProps, DOMProps, AriaLabelingProps, PressEvents {
167
/** The value of the radio button, used when submitting an HTML form */
168
value: string;
169
/** The label for the radio. Accepts any renderable node */
170
children?: ReactNode;
171
/** Whether the radio button is disabled */
172
isDisabled?: boolean;
173
/** Handler called when the radio is pressed */
174
onPress?: (e: PressEvent) => void;
175
/** Handler called when a press interaction starts */
176
onPressStart?: (e: PressEvent) => void;
177
/** Handler called when a press interaction ends */
178
onPressEnd?: (e: PressEvent) => void;
179
/** Handler called when the press state changes */
180
onPressChange?: (isPressed: boolean) => void;
181
/** Handler called when a press is released over the target */
182
onPressUp?: (e: PressEvent) => void;
183
/** Handler called when the element is clicked */
184
onClick?: (e: MouseEvent) => void;
185
}
186
187
interface RadioAria {
188
/** Props for the label wrapper element */
189
labelProps: LabelHTMLAttributes<HTMLLabelElement>;
190
/** Props for the input element */
191
inputProps: InputHTMLAttributes<HTMLInputElement>;
192
/** Whether the radio is disabled */
193
isDisabled: boolean;
194
/** Whether the radio is currently selected */
195
isSelected: boolean;
196
/** Whether the radio is in a pressed state */
197
isPressed: boolean;
198
}
199
```
200
201
**Usage Example:**
202
203
```typescript
204
import { useRadio } from "@react-aria/radio";
205
import { useRef } from "react";
206
207
function Radio({ children, ...props }) {
208
const ref = useRef();
209
const { inputProps, labelProps, isSelected, isDisabled, isPressed } = useRadio(props, state, ref);
210
211
return (
212
<label
213
{...labelProps}
214
className={`radio ${isSelected ? 'selected' : ''} ${isDisabled ? 'disabled' : ''}`}
215
>
216
<input {...inputProps} ref={ref} />
217
<span className="radio-indicator" />
218
{children}
219
</label>
220
);
221
}
222
```
223
224
## Types
225
226
```typescript { .api }
227
/** Layout orientation for radio group */
228
type Orientation = 'horizontal' | 'vertical';
229
230
/** React node type */
231
type ReactNode = React.ReactNode;
232
233
/** Mouse event type */
234
type MouseEvent = React.MouseEvent;
235
236
/** State management for radio groups from @react-stately/radio */
237
interface RadioGroupState {
238
/** Currently selected value */
239
selectedValue: string | null;
240
/** Default selected value */
241
defaultSelectedValue: string | null;
242
/** Last focused value for keyboard navigation */
243
lastFocusedValue: string | null;
244
/** Whether the radio group is disabled */
245
isDisabled: boolean;
246
/** Whether the radio group is read-only */
247
isReadOnly: boolean;
248
/** Whether selection is required */
249
isRequired: boolean;
250
/** Whether the radio group is invalid */
251
isInvalid: boolean;
252
/** Current validation state */
253
displayValidation: ValidationResult;
254
/** Set the selected value */
255
setSelectedValue: (value: string) => void;
256
/** Set the last focused value */
257
setLastFocusedValue: (value: string | null) => void;
258
}
259
260
/** Base props for radio groups */
261
interface RadioGroupProps {
262
/** Currently selected value */
263
value?: string | null;
264
/** Default selected value for uncontrolled components */
265
defaultValue?: string | null;
266
/** Handler called when selection changes */
267
onChange?: (value: string) => void;
268
}
269
270
/** Base props for individual radio buttons */
271
interface RadioProps {
272
/** The value of the radio button */
273
value: string;
274
/** The label content */
275
children?: ReactNode;
276
/** Whether the radio is disabled */
277
isDisabled?: boolean;
278
}
279
280
/** DOM input properties */
281
interface InputDOMProps {
282
/** Form name attribute */
283
name?: string;
284
/** Form element */
285
form?: string;
286
}
287
288
/** Validation properties for ARIA */
289
interface AriaValidationProps {
290
/** Whether validation is required */
291
isRequired?: boolean;
292
/** Validation behavior */
293
validationBehavior?: 'aria' | 'native';
294
/** Custom validation function */
295
validate?: (value: string | null) => ValidationError | true | null | undefined;
296
/** Error message */
297
errorMessage?: string | ((validation: ValidationResult) => string);
298
}
299
300
/** ARIA labeling properties */
301
interface AriaLabelingProps {
302
/** Accessible label */
303
'aria-label'?: string;
304
/** ID of element that labels this element */
305
'aria-labelledby'?: string;
306
/** ID of element that describes this element */
307
'aria-describedby'?: string;
308
}
309
310
/** DOM properties */
311
interface DOMProps {
312
/** Element ID */
313
id?: string;
314
}
315
316
/** Press event handlers */
317
interface PressEvents {
318
/** Handler called when a press interaction starts */
319
onPressStart?: (e: PressEvent) => void;
320
/** Handler called when a press interaction ends */
321
onPressEnd?: (e: PressEvent) => void;
322
/** Handler called when the press state changes */
323
onPressChange?: (isPressed: boolean) => void;
324
/** Handler called when a press is released over the target */
325
onPressUp?: (e: PressEvent) => void;
326
/** Handler called when the element is pressed */
327
onPress?: (e: PressEvent) => void;
328
}
329
330
/** Validation result containing error information */
331
interface ValidationResult {
332
/** Whether the input is invalid */
333
isInvalid: boolean;
334
/** Array of validation error messages */
335
validationErrors: string[];
336
/** Detailed validation information */
337
validationDetails: ValidationDetails;
338
}
339
340
/** Detailed validation information */
341
interface ValidationDetails {
342
/** Validation errors from form constraints */
343
formErrors: string[];
344
/** Validation errors from custom validation */
345
validationErrors: string[];
346
}
347
348
/** Validation error */
349
interface ValidationError {
350
/** Error message */
351
message: string;
352
}
353
354
/** DOM attributes for elements */
355
interface DOMAttributes {
356
[key: string]: any;
357
}
358
359
/** Press event information */
360
interface PressEvent {
361
/** The type of press event */
362
type: 'pressstart' | 'pressend' | 'pressup' | 'press';
363
/** The pointer type that triggered the press event */
364
pointerType: 'mouse' | 'pen' | 'touch' | 'keyboard' | 'virtual';
365
/** The target element of the press event */
366
target: Element;
367
/** Whether the shift key was held during the press event */
368
shiftKey: boolean;
369
/** Whether the ctrl key was held during the press event */
370
ctrlKey: boolean;
371
/** Whether the meta key was held during the press event */
372
metaKey: boolean;
373
/** Whether the alt key was held during the press event */
374
altKey: boolean;
375
}
376
377
/** React ref object */
378
interface RefObject<T> {
379
readonly current: T | null;
380
}
381
382
/** HTML element attributes */
383
interface HTMLAttributes<T> {
384
[key: string]: any;
385
}
386
387
/** HTML label element attributes */
388
interface LabelHTMLAttributes<T> extends HTMLAttributes<T> {
389
htmlFor?: string;
390
}
391
392
/** HTML input element attributes */
393
interface InputHTMLAttributes<T> extends HTMLAttributes<T> {
394
type?: string;
395
name?: string;
396
value?: string | ReadonlyArray<string> | number;
397
checked?: boolean;
398
disabled?: boolean;
399
required?: boolean;
400
tabIndex?: number;
401
}
402
```
403
404
## Error Handling
405
406
The hooks handle validation and error states through the ValidationResult interface:
407
408
- **isInvalid**: Boolean indicating validation failure
409
- **validationErrors**: Array of error messages for display
410
- **validationDetails**: Detailed validation information
411
412
Common validation scenarios include:
413
- Required radio group with no selection
414
- Custom validation rules through the `validate` prop
415
- Form constraint validation
416
417
## Integration Requirements
418
419
@react-aria/radio is designed to work in conjunction with @react-stately/radio for state management. This separation allows for flexible state handling while the aria package focuses purely on accessibility and behavior.
420
421
**Installation:**
422
```bash
423
npm install @react-aria/radio @react-stately/radio
424
```
425
426
**Basic state setup:**
427
```typescript
428
import { useRadioGroupState, type RadioGroupState } from "@react-stately/radio";
429
430
const state = useRadioGroupState({
431
value: selectedValue,
432
onChange: setSelectedValue,
433
defaultValue: 'initial',
434
isDisabled: false,
435
isRequired: true
436
});
437
```
438
439
**Required peer dependencies:**
440
- `@react-stately/radio` - Provides RadioGroupState and useRadioGroupState hook
441
- `react` - React 16.8+ with hooks support (required for hook functionality)
442
- `react-dom` - DOM utilities and event handling
443
444
The package integrates seamlessly with HTML forms and supports both controlled and uncontrolled usage patterns. The aria hooks handle all accessibility concerns while state management is handled separately by the stately package.