- Spec files
npm-react-aria
Describes: pkg:npm/react-aria@3.43.x
- Description
- Comprehensive library of unstyled React hooks providing accessible UI primitives with full WAI-ARIA compliance and internationalization support.
- Author
- tessl
- Last updated
utilities.md docs/
1# Utilities23Utility 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.45## Capabilities67### Prop Management89Utilities for merging and managing component props safely.1011```typescript { .api }12/**13* Merges multiple props objects, handling event handlers and class names properly14* @param props - Props objects to merge15* @returns Merged props object16*/17function mergeProps(...props: any[]): any;18```1920**Usage Examples:**2122```typescript23import { mergeProps } from "react-aria";2425// Merge props from multiple sources26function CustomButton(props) {27const baseProps = {28className: 'btn',29onClick: () => console.log('Base click')30};3132const userProps = props;3334const { buttonProps } = useButton(props, ref);3536// Safely merge all props37const mergedProps = mergeProps(baseProps, buttonProps, userProps);3839return <button {...mergedProps} ref={ref} />;40}4142// Merge event handlers43function EventHandlingComponent(props) {44const internalHandler = (e) => {45console.log('Internal handler');46// Internal logic47};4849const mergedProps = mergeProps(50{ onClick: internalHandler },51{ onClick: props.onClick }52);5354// Both handlers will be called55return <button {...mergedProps}>Click me</button>;56}5758// Merge class names59function StyledComponent(props) {60const defaultProps = { className: 'default-class' };61const stateProps = { className: props.isActive ? 'active' : '' };62const userProps = { className: props.className };6364const merged = mergeProps(defaultProps, stateProps, userProps);65// className will be: 'default-class active user-class'6667return <div {...merged}>{props.children}</div>;68}69```7071### Function Chaining7273Utility for chaining multiple callback functions safely.7475```typescript { .api }76/**77* Chains multiple callback functions into a single function78* @param callbacks - Functions to chain together79* @returns Chained function that calls all callbacks80*/81function chain(...callbacks: any[]): (...args: any[]) => void;82```8384**Usage Examples:**8586```typescript87import { chain } from "react-aria";8889// Chain multiple event handlers90function MultiHandlerButton(props) {91const handleClick = (e) => {92console.log('Button clicked');93};9495const handleAnalytics = (e) => {96analytics.track('button_click');97};9899// Chain handlers together100const chainedHandler = chain(handleClick, handleAnalytics, props.onClick);101102return <button onClick={chainedHandler}>Click me</button>;103}104105// Chain with conditional handlers106function ConditionalChain(props) {107const baseHandler = () => console.log('Base');108109const conditionalHandler = props.debug ?110() => console.log('Debug mode') :111null;112113// chain ignores null/undefined functions114const handler = chain(baseHandler, conditionalHandler, props.onAction);115116return <button onClick={handler}>Action</button>;117}118119// Chain cleanup functions120function useMultipleEffects() {121useEffect(() => {122const cleanup1 = setupEffect1();123const cleanup2 = setupEffect2();124const cleanup3 = setupEffect3();125126// Chain all cleanup functions127return chain(cleanup1, cleanup2, cleanup3);128}, []);129}130```131132### ID Generation133134Utilities for generating unique IDs for accessibility and component relationships.135136```typescript { .api }137/**138* Generates a unique ID, with optional prefix139* @param defaultId - Default ID to use if provided140* @returns Unique ID string141*/142function useId(defaultId?: string): string;143```144145**Usage Examples:**146147```typescript148import { useId } from "react-aria";149150// Generate unique IDs for form fields151function FormField({ label, children, id: userProvidedId }) {152const id = useId(userProvidedId);153const descriptionId = useId();154155return (156<div>157<label htmlFor={id}>{label}</label>158{React.cloneElement(children, {159id,160'aria-describedby': descriptionId161})}162<div id={descriptionId} className="field-description">163Additional help text164</div>165</div>166);167}168169// Generate IDs for complex components170function TabsComponent({ tabs }) {171const tabListId = useId();172173return (174<div>175<div role="tablist" id={tabListId}>176{tabs.map((tab, index) => {177const tabId = useId();178const panelId = useId();179180return (181<button182key={index}183role="tab"184id={tabId}185aria-controls={panelId}186>187{tab.title}188</button>189);190})}191</div>192193{tabs.map((tab, index) => {194const panelId = useId();195const tabId = useId();196197return (198<div199key={index}200role="tabpanel"201id={panelId}202aria-labelledby={tabId}203>204{tab.content}205</div>206);207})}208</div>209);210}211212// Use provided ID or generate one213function AccessibleComponent({ id, label, children }) {214const componentId = useId(id);215const labelId = useId();216217return (218<div>219<div id={labelId}>{label}</div>220<div221id={componentId}222aria-labelledby={labelId}223>224{children}225</div>226</div>227);228}229```230231### Object Refs232233Utility for creating object refs that can be used with forwarded refs.234235```typescript { .api }236/**237* Creates an object ref that can be used with forwardRef238* @param forwardedRef - Forwarded ref from parent component239* @returns Object ref that can be used in hooks240*/241function useObjectRef<T>(forwardedRef: ForwardedRef<T>): RefObject<T>;242```243244**Usage Examples:**245246```typescript247import React, { forwardRef } from "react";248import { useObjectRef, useButton } from "react-aria";249250// Forward refs properly in custom components251const CustomButton = forwardRef<HTMLButtonElement, ButtonProps>((props, forwardedRef) => {252const ref = useObjectRef(forwardedRef);253const { buttonProps } = useButton(props, ref);254255return (256<button {...buttonProps} ref={ref}>257{props.children}258</button>259);260});261262// Use with multiple hooks that need the same ref263const ComplexInput = forwardRef<HTMLInputElement, InputProps>((props, forwardedRef) => {264const ref = useObjectRef(forwardedRef);265266const { inputProps } = useTextField(props, ref);267const { focusProps } = useFocus({ onFocus: props.onFocus }, ref);268const { hoverProps } = useHover({ onHover: props.onHover }, ref);269270const combinedProps = mergeProps(inputProps, focusProps, hoverProps);271272return <input {...combinedProps} ref={ref} />;273});274275// Imperative handle with object ref276const ImperativeComponent = forwardRef<ComponentHandle, ComponentProps>((props, forwardedRef) => {277const ref = useObjectRef<HTMLDivElement>(null);278279useImperativeHandle(forwardedRef, () => ({280focus: () => ref.current?.focus(),281scrollIntoView: () => ref.current?.scrollIntoView(),282getBoundingClientRect: () => ref.current?.getBoundingClientRect()283}), []);284285return <div ref={ref}>{props.children}</div>;286});287```288289### Router Integration290291Utilities for integrating React Aria with routing libraries.292293```typescript { .api }294/**295* Router provider for React Aria components296* @param props - Router provider configuration297* @returns Provider component298*/299function RouterProvider(props: RouterProviderProps): JSX.Element;300301interface RouterProviderProps {302/** Children to provide router context to */303children: ReactNode;304/** Navigate function from your router */305navigate: (path: string, options?: NavigateOptions) => void;306/** Current pathname */307pathname?: string;308/** Whether router uses hash routing */309useHref?: (href: string) => string;310}311312interface NavigateOptions {313/** Whether to replace current history entry */314replace?: boolean;315/** State to pass with navigation */316state?: any;317}318```319320**Usage Examples:**321322```typescript323import { RouterProvider } from "react-aria";324import { useNavigate, useLocation } from "react-router-dom";325326// React Router integration327function App() {328const navigate = useNavigate();329const location = useLocation();330331return (332<RouterProvider333navigate={navigate}334pathname={location.pathname}335>336<YourApp />337</RouterProvider>338);339}340341// Next.js integration342import { useRouter } from "next/router";343344function MyApp({ Component, pageProps }) {345const router = useRouter();346347return (348<RouterProvider349navigate={(path, options) => {350if (options?.replace) {351router.replace(path);352} else {353router.push(path);354}355}}356pathname={router.asPath}357>358<Component {...pageProps} />359</RouterProvider>360);361}362363// Custom router integration364function CustomRouterApp() {365const [pathname, setPathname] = useState('/');366367const navigate = useCallback((path, options) => {368if (options?.replace) {369history.replaceState(null, '', path);370} else {371history.pushState(null, '', path);372}373setPathname(path);374}, []);375376return (377<RouterProvider378navigate={navigate}379pathname={pathname}380>381<YourApp />382</RouterProvider>383);384}385```386387### SSR Support388389Server-side rendering utilities and components.390391```typescript { .api }392/**393* SSR provider component that handles hydration mismatches394* @param props - SSR provider configuration395* @returns Provider component396*/397function SSRProvider(props: SSRProviderProps): JSX.Element;398399/**400* Hook to detect server-side rendering401* @returns Whether currently running on server402*/403function useIsSSR(): boolean;404405interface SSRProviderProps {406/** Children to provide SSR context to */407children: ReactNode;408}409```410411**Usage Examples:**412413```typescript414import { SSRProvider, useIsSSR } from "react-aria";415416// Wrap your app with SSRProvider417function App() {418return (419<SSRProvider>420<YourApp />421</SSRProvider>422);423}424425// Conditional rendering based on SSR426function ClientOnlyComponent() {427const isSSR = useIsSSR();428429if (isSSR) {430return <div>Loading...</div>;431}432433return <InteractiveComponent />;434}435436// Next.js pages/_app.js437export default function MyApp({ Component, pageProps }) {438return (439<SSRProvider>440<Component {...pageProps} />441</SSRProvider>442);443}444445// Gatsby gatsby-browser.js and gatsby-ssr.js446export const wrapRootElement = ({ element }) => (447<SSRProvider>{element}</SSRProvider>448);449```450451### Visually Hidden452453Utilities for screen reader only content that is visually hidden but accessible to assistive technology.454455```typescript { .api }456/**457* Component that renders content only for screen readers458* @param props - Visually hidden configuration459* @returns Visually hidden element460*/461function VisuallyHidden(props: VisuallyHiddenProps): JSX.Element;462463/**464* Hook for visually hidden element behavior465* @param props - Visually hidden configuration466* @returns Visually hidden props467*/468function useVisuallyHidden(props?: VisuallyHiddenProps): VisuallyHiddenAria;469470interface VisuallyHiddenProps {471/** Children content to hide visually */472children: ReactNode;473/** Element type to render */474elementType?: React.ElementType;475/** Whether to render children inside focusable element */476isFocusable?: boolean;477}478479interface VisuallyHiddenAria {480/** Props for the visually hidden element */481visuallyHiddenProps: DOMAttributes<Element>;482}483```484485**Usage Examples:**486487```typescript488import { VisuallyHidden } from "react-aria";489490// Screen reader only labels491function IconButton({ icon, label, ...props }) {492return (493<button {...props}>494<Icon>{icon}</Icon>495<VisuallyHidden>{label}</VisuallyHidden>496</button>497);498}499500// Skip links for keyboard navigation501function SkipLinks() {502return (503<VisuallyHidden isFocusable>504<a href="#main-content">Skip to main content</a>505</VisuallyHidden>506);507}508509// Accessible table headers510function DataTable() {511return (512<table>513<thead>514<tr>515<th>516Name517<VisuallyHidden>(sortable column)</VisuallyHidden>518</th>519<th>Actions</th>520</tr>521</thead>522</table>523);524}525```526527### Development Utilities528529Utilities for development and debugging.530531```typescript { .api }532/**533* Development warning utility534* @param condition - Condition to check535* @param message - Warning message536*/537function warn(condition: boolean, message: string): void;538539/**540* Development-only component prop validation541* @param props - Props to validate542* @param propTypes - Prop type definitions543* @param componentName - Name of component544*/545function validateProps(props: any, propTypes: any, componentName: string): void;546547/**548* Get display name for debugging549* @param Component - React component550* @returns Display name string551*/552function getDisplayName(Component: React.ComponentType<any>): string;553```554555**Usage Examples:**556557```typescript558import { warn } from "react-aria";559560// Development warnings561function CustomComponent(props) {562if (process.env.NODE_ENV !== 'production') {563warn(564!props.children && !props.label,565'CustomComponent should have either children or a label prop'566);567568warn(569props.isDisabled && props.onPress,570'CustomComponent: onPress will not be called when isDisabled is true'571);572}573574// Component implementation575}576577// Debug component names578function withAriaLabel(Component) {579const WrappedComponent = (props) => {580return <Component {...props} />;581};582583WrappedComponent.displayName = `withAriaLabel(${getDisplayName(Component)})`;584585return WrappedComponent;586}587```588589## Types and Constants590591```typescript { .api }592type ForwardedRef<T> = ((instance: T | null) => void) | MutableRefObject<T | null> | null;593594type RefObject<T> = { readonly current: T | null };595596interface DOMAttributes<T = Element> {597// Event handlers598onFocus?: (e: FocusEvent<T>) => void;599onBlur?: (e: FocusEvent<T>) => void;600onClick?: (e: MouseEvent<T>) => void;601onKeyDown?: (e: KeyboardEvent<T>) => void;602onKeyUp?: (e: KeyboardEvent<T>) => void;603604// ARIA attributes605'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';615616// HTML attributes617id?: string;618className?: string;619role?: string;620tabIndex?: number;621style?: React.CSSProperties;622}623624// Common prop patterns625interface BaseProps {626/** Whether the component is disabled */627isDisabled?: boolean;628/** CSS class name */629className?: string;630/** Inline styles */631style?: React.CSSProperties;632/** Data test ID for testing */633'data-testid'?: string;634}635636interface FocusableProps extends BaseProps {637/** Auto-focus behavior */638autoFocus?: boolean;639/** Exclude from tab order */640excludeFromTabOrder?: boolean;641/** Focus event handlers */642onFocus?: (e: FocusEvent) => void;643onBlur?: (e: FocusEvent) => void;644}645646interface PressableProps extends FocusableProps {647/** Press event handlers */648onPress?: (e: PressEvent) => void;649onPressStart?: (e: PressEvent) => void;650onPressEnd?: (e: PressEvent) => void;651onPressChange?: (isPressed: boolean) => void;652onPressUp?: (e: PressEvent) => void;653}654```