npm-react-aria

Description
Comprehensive library of unstyled React hooks providing accessible UI primitives with full WAI-ARIA compliance and internationalization support.
Author
tessl
Last updated

How to use

npx @tessl/cli registry install tessl/npm-react-aria@3.43.0

utilities.md docs/

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