0
# Utilities
1
2
Utility hooks and functions for common React Aria patterns, prop management, ID generation, and routing integration. These utilities provide foundational functionality used throughout the React Aria ecosystem.
3
4
## Capabilities
5
6
### Prop Management
7
8
Utilities for merging and managing component props safely.
9
10
```typescript { .api }
11
/**
12
* Merges multiple props objects, handling event handlers and class names properly
13
* @param props - Props objects to merge
14
* @returns Merged props object
15
*/
16
function mergeProps(...props: any[]): any;
17
```
18
19
**Usage Examples:**
20
21
```typescript
22
import { mergeProps } from "react-aria";
23
24
// Merge props from multiple sources
25
function CustomButton(props) {
26
const baseProps = {
27
className: 'btn',
28
onClick: () => console.log('Base click')
29
};
30
31
const userProps = props;
32
33
const { buttonProps } = useButton(props, ref);
34
35
// Safely merge all props
36
const mergedProps = mergeProps(baseProps, buttonProps, userProps);
37
38
return <button {...mergedProps} ref={ref} />;
39
}
40
41
// Merge event handlers
42
function EventHandlingComponent(props) {
43
const internalHandler = (e) => {
44
console.log('Internal handler');
45
// Internal logic
46
};
47
48
const mergedProps = mergeProps(
49
{ onClick: internalHandler },
50
{ onClick: props.onClick }
51
);
52
53
// Both handlers will be called
54
return <button {...mergedProps}>Click me</button>;
55
}
56
57
// Merge class names
58
function StyledComponent(props) {
59
const defaultProps = { className: 'default-class' };
60
const stateProps = { className: props.isActive ? 'active' : '' };
61
const userProps = { className: props.className };
62
63
const merged = mergeProps(defaultProps, stateProps, userProps);
64
// className will be: 'default-class active user-class'
65
66
return <div {...merged}>{props.children}</div>;
67
}
68
```
69
70
### Function Chaining
71
72
Utility for chaining multiple callback functions safely.
73
74
```typescript { .api }
75
/**
76
* Chains multiple callback functions into a single function
77
* @param callbacks - Functions to chain together
78
* @returns Chained function that calls all callbacks
79
*/
80
function chain(...callbacks: any[]): (...args: any[]) => void;
81
```
82
83
**Usage Examples:**
84
85
```typescript
86
import { chain } from "react-aria";
87
88
// Chain multiple event handlers
89
function MultiHandlerButton(props) {
90
const handleClick = (e) => {
91
console.log('Button clicked');
92
};
93
94
const handleAnalytics = (e) => {
95
analytics.track('button_click');
96
};
97
98
// Chain handlers together
99
const chainedHandler = chain(handleClick, handleAnalytics, props.onClick);
100
101
return <button onClick={chainedHandler}>Click me</button>;
102
}
103
104
// Chain with conditional handlers
105
function ConditionalChain(props) {
106
const baseHandler = () => console.log('Base');
107
108
const conditionalHandler = props.debug ?
109
() => console.log('Debug mode') :
110
null;
111
112
// chain ignores null/undefined functions
113
const handler = chain(baseHandler, conditionalHandler, props.onAction);
114
115
return <button onClick={handler}>Action</button>;
116
}
117
118
// Chain cleanup functions
119
function useMultipleEffects() {
120
useEffect(() => {
121
const cleanup1 = setupEffect1();
122
const cleanup2 = setupEffect2();
123
const cleanup3 = setupEffect3();
124
125
// Chain all cleanup functions
126
return chain(cleanup1, cleanup2, cleanup3);
127
}, []);
128
}
129
```
130
131
### ID Generation
132
133
Utilities for generating unique IDs for accessibility and component relationships.
134
135
```typescript { .api }
136
/**
137
* Generates a unique ID, with optional prefix
138
* @param defaultId - Default ID to use if provided
139
* @returns Unique ID string
140
*/
141
function useId(defaultId?: string): string;
142
```
143
144
**Usage Examples:**
145
146
```typescript
147
import { useId } from "react-aria";
148
149
// Generate unique IDs for form fields
150
function FormField({ label, children, id: userProvidedId }) {
151
const id = useId(userProvidedId);
152
const descriptionId = useId();
153
154
return (
155
<div>
156
<label htmlFor={id}>{label}</label>
157
{React.cloneElement(children, {
158
id,
159
'aria-describedby': descriptionId
160
})}
161
<div id={descriptionId} className="field-description">
162
Additional help text
163
</div>
164
</div>
165
);
166
}
167
168
// Generate IDs for complex components
169
function TabsComponent({ tabs }) {
170
const tabListId = useId();
171
172
return (
173
<div>
174
<div role="tablist" id={tabListId}>
175
{tabs.map((tab, index) => {
176
const tabId = useId();
177
const panelId = useId();
178
179
return (
180
<button
181
key={index}
182
role="tab"
183
id={tabId}
184
aria-controls={panelId}
185
>
186
{tab.title}
187
</button>
188
);
189
})}
190
</div>
191
192
{tabs.map((tab, index) => {
193
const panelId = useId();
194
const tabId = useId();
195
196
return (
197
<div
198
key={index}
199
role="tabpanel"
200
id={panelId}
201
aria-labelledby={tabId}
202
>
203
{tab.content}
204
</div>
205
);
206
})}
207
</div>
208
);
209
}
210
211
// Use provided ID or generate one
212
function AccessibleComponent({ id, label, children }) {
213
const componentId = useId(id);
214
const labelId = useId();
215
216
return (
217
<div>
218
<div id={labelId}>{label}</div>
219
<div
220
id={componentId}
221
aria-labelledby={labelId}
222
>
223
{children}
224
</div>
225
</div>
226
);
227
}
228
```
229
230
### Object Refs
231
232
Utility for creating object refs that can be used with forwarded refs.
233
234
```typescript { .api }
235
/**
236
* Creates an object ref that can be used with forwardRef
237
* @param forwardedRef - Forwarded ref from parent component
238
* @returns Object ref that can be used in hooks
239
*/
240
function useObjectRef<T>(forwardedRef: ForwardedRef<T>): RefObject<T>;
241
```
242
243
**Usage Examples:**
244
245
```typescript
246
import React, { forwardRef } from "react";
247
import { useObjectRef, useButton } from "react-aria";
248
249
// Forward refs properly in custom components
250
const CustomButton = forwardRef<HTMLButtonElement, ButtonProps>((props, forwardedRef) => {
251
const ref = useObjectRef(forwardedRef);
252
const { buttonProps } = useButton(props, ref);
253
254
return (
255
<button {...buttonProps} ref={ref}>
256
{props.children}
257
</button>
258
);
259
});
260
261
// Use with multiple hooks that need the same ref
262
const ComplexInput = forwardRef<HTMLInputElement, InputProps>((props, forwardedRef) => {
263
const ref = useObjectRef(forwardedRef);
264
265
const { inputProps } = useTextField(props, ref);
266
const { focusProps } = useFocus({ onFocus: props.onFocus }, ref);
267
const { hoverProps } = useHover({ onHover: props.onHover }, ref);
268
269
const combinedProps = mergeProps(inputProps, focusProps, hoverProps);
270
271
return <input {...combinedProps} ref={ref} />;
272
});
273
274
// Imperative handle with object ref
275
const ImperativeComponent = forwardRef<ComponentHandle, ComponentProps>((props, forwardedRef) => {
276
const ref = useObjectRef<HTMLDivElement>(null);
277
278
useImperativeHandle(forwardedRef, () => ({
279
focus: () => ref.current?.focus(),
280
scrollIntoView: () => ref.current?.scrollIntoView(),
281
getBoundingClientRect: () => ref.current?.getBoundingClientRect()
282
}), []);
283
284
return <div ref={ref}>{props.children}</div>;
285
});
286
```
287
288
### Router Integration
289
290
Utilities for integrating React Aria with routing libraries.
291
292
```typescript { .api }
293
/**
294
* Router provider for React Aria components
295
* @param props - Router provider configuration
296
* @returns Provider component
297
*/
298
function RouterProvider(props: RouterProviderProps): JSX.Element;
299
300
interface RouterProviderProps {
301
/** Children to provide router context to */
302
children: ReactNode;
303
/** Navigate function from your router */
304
navigate: (path: string, options?: NavigateOptions) => void;
305
/** Current pathname */
306
pathname?: string;
307
/** Whether router uses hash routing */
308
useHref?: (href: string) => string;
309
}
310
311
interface NavigateOptions {
312
/** Whether to replace current history entry */
313
replace?: boolean;
314
/** State to pass with navigation */
315
state?: any;
316
}
317
```
318
319
**Usage Examples:**
320
321
```typescript
322
import { RouterProvider } from "react-aria";
323
import { useNavigate, useLocation } from "react-router-dom";
324
325
// React Router integration
326
function App() {
327
const navigate = useNavigate();
328
const location = useLocation();
329
330
return (
331
<RouterProvider
332
navigate={navigate}
333
pathname={location.pathname}
334
>
335
<YourApp />
336
</RouterProvider>
337
);
338
}
339
340
// Next.js integration
341
import { useRouter } from "next/router";
342
343
function MyApp({ Component, pageProps }) {
344
const router = useRouter();
345
346
return (
347
<RouterProvider
348
navigate={(path, options) => {
349
if (options?.replace) {
350
router.replace(path);
351
} else {
352
router.push(path);
353
}
354
}}
355
pathname={router.asPath}
356
>
357
<Component {...pageProps} />
358
</RouterProvider>
359
);
360
}
361
362
// Custom router integration
363
function CustomRouterApp() {
364
const [pathname, setPathname] = useState('/');
365
366
const navigate = useCallback((path, options) => {
367
if (options?.replace) {
368
history.replaceState(null, '', path);
369
} else {
370
history.pushState(null, '', path);
371
}
372
setPathname(path);
373
}, []);
374
375
return (
376
<RouterProvider
377
navigate={navigate}
378
pathname={pathname}
379
>
380
<YourApp />
381
</RouterProvider>
382
);
383
}
384
```
385
386
### SSR Support
387
388
Server-side rendering utilities and components.
389
390
```typescript { .api }
391
/**
392
* SSR provider component that handles hydration mismatches
393
* @param props - SSR provider configuration
394
* @returns Provider component
395
*/
396
function SSRProvider(props: SSRProviderProps): JSX.Element;
397
398
/**
399
* Hook to detect server-side rendering
400
* @returns Whether currently running on server
401
*/
402
function useIsSSR(): boolean;
403
404
interface SSRProviderProps {
405
/** Children to provide SSR context to */
406
children: ReactNode;
407
}
408
```
409
410
**Usage Examples:**
411
412
```typescript
413
import { SSRProvider, useIsSSR } from "react-aria";
414
415
// Wrap your app with SSRProvider
416
function App() {
417
return (
418
<SSRProvider>
419
<YourApp />
420
</SSRProvider>
421
);
422
}
423
424
// Conditional rendering based on SSR
425
function ClientOnlyComponent() {
426
const isSSR = useIsSSR();
427
428
if (isSSR) {
429
return <div>Loading...</div>;
430
}
431
432
return <InteractiveComponent />;
433
}
434
435
// Next.js pages/_app.js
436
export default function MyApp({ Component, pageProps }) {
437
return (
438
<SSRProvider>
439
<Component {...pageProps} />
440
</SSRProvider>
441
);
442
}
443
444
// Gatsby gatsby-browser.js and gatsby-ssr.js
445
export const wrapRootElement = ({ element }) => (
446
<SSRProvider>{element}</SSRProvider>
447
);
448
```
449
450
### Visually Hidden
451
452
Utilities for screen reader only content that is visually hidden but accessible to assistive technology.
453
454
```typescript { .api }
455
/**
456
* Component that renders content only for screen readers
457
* @param props - Visually hidden configuration
458
* @returns Visually hidden element
459
*/
460
function VisuallyHidden(props: VisuallyHiddenProps): JSX.Element;
461
462
/**
463
* Hook for visually hidden element behavior
464
* @param props - Visually hidden configuration
465
* @returns Visually hidden props
466
*/
467
function useVisuallyHidden(props?: VisuallyHiddenProps): VisuallyHiddenAria;
468
469
interface VisuallyHiddenProps {
470
/** Children content to hide visually */
471
children: ReactNode;
472
/** Element type to render */
473
elementType?: React.ElementType;
474
/** Whether to render children inside focusable element */
475
isFocusable?: boolean;
476
}
477
478
interface VisuallyHiddenAria {
479
/** Props for the visually hidden element */
480
visuallyHiddenProps: DOMAttributes<Element>;
481
}
482
```
483
484
**Usage Examples:**
485
486
```typescript
487
import { VisuallyHidden } from "react-aria";
488
489
// Screen reader only labels
490
function IconButton({ icon, label, ...props }) {
491
return (
492
<button {...props}>
493
<Icon>{icon}</Icon>
494
<VisuallyHidden>{label}</VisuallyHidden>
495
</button>
496
);
497
}
498
499
// Skip links for keyboard navigation
500
function SkipLinks() {
501
return (
502
<VisuallyHidden isFocusable>
503
<a href="#main-content">Skip to main content</a>
504
</VisuallyHidden>
505
);
506
}
507
508
// Accessible table headers
509
function DataTable() {
510
return (
511
<table>
512
<thead>
513
<tr>
514
<th>
515
Name
516
<VisuallyHidden>(sortable column)</VisuallyHidden>
517
</th>
518
<th>Actions</th>
519
</tr>
520
</thead>
521
</table>
522
);
523
}
524
```
525
526
### Development Utilities
527
528
Utilities for development and debugging.
529
530
```typescript { .api }
531
/**
532
* Development warning utility
533
* @param condition - Condition to check
534
* @param message - Warning message
535
*/
536
function warn(condition: boolean, message: string): void;
537
538
/**
539
* Development-only component prop validation
540
* @param props - Props to validate
541
* @param propTypes - Prop type definitions
542
* @param componentName - Name of component
543
*/
544
function validateProps(props: any, propTypes: any, componentName: string): void;
545
546
/**
547
* Get display name for debugging
548
* @param Component - React component
549
* @returns Display name string
550
*/
551
function getDisplayName(Component: React.ComponentType<any>): string;
552
```
553
554
**Usage Examples:**
555
556
```typescript
557
import { warn } from "react-aria";
558
559
// Development warnings
560
function CustomComponent(props) {
561
if (process.env.NODE_ENV !== 'production') {
562
warn(
563
!props.children && !props.label,
564
'CustomComponent should have either children or a label prop'
565
);
566
567
warn(
568
props.isDisabled && props.onPress,
569
'CustomComponent: onPress will not be called when isDisabled is true'
570
);
571
}
572
573
// Component implementation
574
}
575
576
// Debug component names
577
function withAriaLabel(Component) {
578
const WrappedComponent = (props) => {
579
return <Component {...props} />;
580
};
581
582
WrappedComponent.displayName = `withAriaLabel(${getDisplayName(Component)})`;
583
584
return WrappedComponent;
585
}
586
```
587
588
## Types and Constants
589
590
```typescript { .api }
591
type ForwardedRef<T> = ((instance: T | null) => void) | MutableRefObject<T | null> | null;
592
593
type RefObject<T> = { readonly current: T | null };
594
595
interface DOMAttributes<T = Element> {
596
// Event handlers
597
onFocus?: (e: FocusEvent<T>) => void;
598
onBlur?: (e: FocusEvent<T>) => void;
599
onClick?: (e: MouseEvent<T>) => void;
600
onKeyDown?: (e: KeyboardEvent<T>) => void;
601
onKeyUp?: (e: KeyboardEvent<T>) => void;
602
603
// ARIA attributes
604
'aria-label'?: string;
605
'aria-labelledby'?: string;
606
'aria-describedby'?: string;
607
'aria-expanded'?: boolean;
608
'aria-selected'?: boolean;
609
'aria-checked'?: boolean;
610
'aria-disabled'?: boolean;
611
'aria-hidden'?: boolean;
612
'aria-pressed'?: boolean;
613
'aria-current'?: boolean | 'page' | 'step' | 'location' | 'date' | 'time';
614
615
// HTML attributes
616
id?: string;
617
className?: string;
618
role?: string;
619
tabIndex?: number;
620
style?: React.CSSProperties;
621
}
622
623
// Common prop patterns
624
interface BaseProps {
625
/** Whether the component is disabled */
626
isDisabled?: boolean;
627
/** CSS class name */
628
className?: string;
629
/** Inline styles */
630
style?: React.CSSProperties;
631
/** Data test ID for testing */
632
'data-testid'?: string;
633
}
634
635
interface FocusableProps extends BaseProps {
636
/** Auto-focus behavior */
637
autoFocus?: boolean;
638
/** Exclude from tab order */
639
excludeFromTabOrder?: boolean;
640
/** Focus event handlers */
641
onFocus?: (e: FocusEvent) => void;
642
onBlur?: (e: FocusEvent) => void;
643
}
644
645
interface PressableProps extends FocusableProps {
646
/** Press event handlers */
647
onPress?: (e: PressEvent) => void;
648
onPressStart?: (e: PressEvent) => void;
649
onPressEnd?: (e: PressEvent) => void;
650
onPressChange?: (isPressed: boolean) => void;
651
onPressUp?: (e: PressEvent) => void;
652
}
653
```