Comprehensive library of unstyled React hooks providing accessible UI primitives with full WAI-ARIA compliance and internationalization support.
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
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.
Utilities for merging and managing component props safely.
/**
* Merges multiple props objects, handling event handlers and class names properly
* @param props - Props objects to merge
* @returns Merged props object
*/
function mergeProps(...props: any[]): any;Usage Examples:
import { mergeProps } from "react-aria";
// Merge props from multiple sources
function CustomButton(props) {
const baseProps = {
className: 'btn',
onClick: () => console.log('Base click')
};
const userProps = props;
const { buttonProps } = useButton(props, ref);
// Safely merge all props
const mergedProps = mergeProps(baseProps, buttonProps, userProps);
return <button {...mergedProps} ref={ref} />;
}
// Merge event handlers
function EventHandlingComponent(props) {
const internalHandler = (e) => {
console.log('Internal handler');
// Internal logic
};
const mergedProps = mergeProps(
{ onClick: internalHandler },
{ onClick: props.onClick }
);
// Both handlers will be called
return <button {...mergedProps}>Click me</button>;
}
// Merge class names
function StyledComponent(props) {
const defaultProps = { className: 'default-class' };
const stateProps = { className: props.isActive ? 'active' : '' };
const userProps = { className: props.className };
const merged = mergeProps(defaultProps, stateProps, userProps);
// className will be: 'default-class active user-class'
return <div {...merged}>{props.children}</div>;
}Utility for chaining multiple callback functions safely.
/**
* Chains multiple callback functions into a single function
* @param callbacks - Functions to chain together
* @returns Chained function that calls all callbacks
*/
function chain(...callbacks: any[]): (...args: any[]) => void;Usage Examples:
import { chain } from "react-aria";
// Chain multiple event handlers
function MultiHandlerButton(props) {
const handleClick = (e) => {
console.log('Button clicked');
};
const handleAnalytics = (e) => {
analytics.track('button_click');
};
// Chain handlers together
const chainedHandler = chain(handleClick, handleAnalytics, props.onClick);
return <button onClick={chainedHandler}>Click me</button>;
}
// Chain with conditional handlers
function ConditionalChain(props) {
const baseHandler = () => console.log('Base');
const conditionalHandler = props.debug ?
() => console.log('Debug mode') :
null;
// chain ignores null/undefined functions
const handler = chain(baseHandler, conditionalHandler, props.onAction);
return <button onClick={handler}>Action</button>;
}
// Chain cleanup functions
function useMultipleEffects() {
useEffect(() => {
const cleanup1 = setupEffect1();
const cleanup2 = setupEffect2();
const cleanup3 = setupEffect3();
// Chain all cleanup functions
return chain(cleanup1, cleanup2, cleanup3);
}, []);
}Utilities for generating unique IDs for accessibility and component relationships.
/**
* Generates a unique ID, with optional prefix
* @param defaultId - Default ID to use if provided
* @returns Unique ID string
*/
function useId(defaultId?: string): string;Usage Examples:
import { useId } from "react-aria";
// Generate unique IDs for form fields
function FormField({ label, children, id: userProvidedId }) {
const id = useId(userProvidedId);
const descriptionId = useId();
return (
<div>
<label htmlFor={id}>{label}</label>
{React.cloneElement(children, {
id,
'aria-describedby': descriptionId
})}
<div id={descriptionId} className="field-description">
Additional help text
</div>
</div>
);
}
// Generate IDs for complex components
function TabsComponent({ tabs }) {
const tabListId = useId();
return (
<div>
<div role="tablist" id={tabListId}>
{tabs.map((tab, index) => {
const tabId = useId();
const panelId = useId();
return (
<button
key={index}
role="tab"
id={tabId}
aria-controls={panelId}
>
{tab.title}
</button>
);
})}
</div>
{tabs.map((tab, index) => {
const panelId = useId();
const tabId = useId();
return (
<div
key={index}
role="tabpanel"
id={panelId}
aria-labelledby={tabId}
>
{tab.content}
</div>
);
})}
</div>
);
}
// Use provided ID or generate one
function AccessibleComponent({ id, label, children }) {
const componentId = useId(id);
const labelId = useId();
return (
<div>
<div id={labelId}>{label}</div>
<div
id={componentId}
aria-labelledby={labelId}
>
{children}
</div>
</div>
);
}Utility for creating object refs that can be used with forwarded refs.
/**
* Creates an object ref that can be used with forwardRef
* @param forwardedRef - Forwarded ref from parent component
* @returns Object ref that can be used in hooks
*/
function useObjectRef<T>(forwardedRef: ForwardedRef<T>): RefObject<T>;Usage Examples:
import React, { forwardRef } from "react";
import { useObjectRef, useButton } from "react-aria";
// Forward refs properly in custom components
const CustomButton = forwardRef<HTMLButtonElement, ButtonProps>((props, forwardedRef) => {
const ref = useObjectRef(forwardedRef);
const { buttonProps } = useButton(props, ref);
return (
<button {...buttonProps} ref={ref}>
{props.children}
</button>
);
});
// Use with multiple hooks that need the same ref
const ComplexInput = forwardRef<HTMLInputElement, InputProps>((props, forwardedRef) => {
const ref = useObjectRef(forwardedRef);
const { inputProps } = useTextField(props, ref);
const { focusProps } = useFocus({ onFocus: props.onFocus }, ref);
const { hoverProps } = useHover({ onHover: props.onHover }, ref);
const combinedProps = mergeProps(inputProps, focusProps, hoverProps);
return <input {...combinedProps} ref={ref} />;
});
// Imperative handle with object ref
const ImperativeComponent = forwardRef<ComponentHandle, ComponentProps>((props, forwardedRef) => {
const ref = useObjectRef<HTMLDivElement>(null);
useImperativeHandle(forwardedRef, () => ({
focus: () => ref.current?.focus(),
scrollIntoView: () => ref.current?.scrollIntoView(),
getBoundingClientRect: () => ref.current?.getBoundingClientRect()
}), []);
return <div ref={ref}>{props.children}</div>;
});Utilities for integrating React Aria with routing libraries.
/**
* Router provider for React Aria components
* @param props - Router provider configuration
* @returns Provider component
*/
function RouterProvider(props: RouterProviderProps): JSX.Element;
interface RouterProviderProps {
/** Children to provide router context to */
children: ReactNode;
/** Navigate function from your router */
navigate: (path: string, options?: NavigateOptions) => void;
/** Current pathname */
pathname?: string;
/** Whether router uses hash routing */
useHref?: (href: string) => string;
}
interface NavigateOptions {
/** Whether to replace current history entry */
replace?: boolean;
/** State to pass with navigation */
state?: any;
}Usage Examples:
import { RouterProvider } from "react-aria";
import { useNavigate, useLocation } from "react-router-dom";
// React Router integration
function App() {
const navigate = useNavigate();
const location = useLocation();
return (
<RouterProvider
navigate={navigate}
pathname={location.pathname}
>
<YourApp />
</RouterProvider>
);
}
// Next.js integration
import { useRouter } from "next/router";
function MyApp({ Component, pageProps }) {
const router = useRouter();
return (
<RouterProvider
navigate={(path, options) => {
if (options?.replace) {
router.replace(path);
} else {
router.push(path);
}
}}
pathname={router.asPath}
>
<Component {...pageProps} />
</RouterProvider>
);
}
// Custom router integration
function CustomRouterApp() {
const [pathname, setPathname] = useState('/');
const navigate = useCallback((path, options) => {
if (options?.replace) {
history.replaceState(null, '', path);
} else {
history.pushState(null, '', path);
}
setPathname(path);
}, []);
return (
<RouterProvider
navigate={navigate}
pathname={pathname}
>
<YourApp />
</RouterProvider>
);
}Server-side rendering utilities and components.
/**
* SSR provider component that handles hydration mismatches
* @param props - SSR provider configuration
* @returns Provider component
*/
function SSRProvider(props: SSRProviderProps): JSX.Element;
/**
* Hook to detect server-side rendering
* @returns Whether currently running on server
*/
function useIsSSR(): boolean;
interface SSRProviderProps {
/** Children to provide SSR context to */
children: ReactNode;
}Usage Examples:
import { SSRProvider, useIsSSR } from "react-aria";
// Wrap your app with SSRProvider
function App() {
return (
<SSRProvider>
<YourApp />
</SSRProvider>
);
}
// Conditional rendering based on SSR
function ClientOnlyComponent() {
const isSSR = useIsSSR();
if (isSSR) {
return <div>Loading...</div>;
}
return <InteractiveComponent />;
}
// Next.js pages/_app.js
export default function MyApp({ Component, pageProps }) {
return (
<SSRProvider>
<Component {...pageProps} />
</SSRProvider>
);
}
// Gatsby gatsby-browser.js and gatsby-ssr.js
export const wrapRootElement = ({ element }) => (
<SSRProvider>{element}</SSRProvider>
);Utilities for screen reader only content that is visually hidden but accessible to assistive technology.
/**
* Component that renders content only for screen readers
* @param props - Visually hidden configuration
* @returns Visually hidden element
*/
function VisuallyHidden(props: VisuallyHiddenProps): JSX.Element;
/**
* Hook for visually hidden element behavior
* @param props - Visually hidden configuration
* @returns Visually hidden props
*/
function useVisuallyHidden(props?: VisuallyHiddenProps): VisuallyHiddenAria;
interface VisuallyHiddenProps {
/** Children content to hide visually */
children: ReactNode;
/** Element type to render */
elementType?: React.ElementType;
/** Whether to render children inside focusable element */
isFocusable?: boolean;
}
interface VisuallyHiddenAria {
/** Props for the visually hidden element */
visuallyHiddenProps: DOMAttributes<Element>;
}Usage Examples:
import { VisuallyHidden } from "react-aria";
// Screen reader only labels
function IconButton({ icon, label, ...props }) {
return (
<button {...props}>
<Icon>{icon}</Icon>
<VisuallyHidden>{label}</VisuallyHidden>
</button>
);
}
// Skip links for keyboard navigation
function SkipLinks() {
return (
<VisuallyHidden isFocusable>
<a href="#main-content">Skip to main content</a>
</VisuallyHidden>
);
}
// Accessible table headers
function DataTable() {
return (
<table>
<thead>
<tr>
<th>
Name
<VisuallyHidden>(sortable column)</VisuallyHidden>
</th>
<th>Actions</th>
</tr>
</thead>
</table>
);
}Utilities for development and debugging.
/**
* Development warning utility
* @param condition - Condition to check
* @param message - Warning message
*/
function warn(condition: boolean, message: string): void;
/**
* Development-only component prop validation
* @param props - Props to validate
* @param propTypes - Prop type definitions
* @param componentName - Name of component
*/
function validateProps(props: any, propTypes: any, componentName: string): void;
/**
* Get display name for debugging
* @param Component - React component
* @returns Display name string
*/
function getDisplayName(Component: React.ComponentType<any>): string;Usage Examples:
import { warn } from "react-aria";
// Development warnings
function CustomComponent(props) {
if (process.env.NODE_ENV !== 'production') {
warn(
!props.children && !props.label,
'CustomComponent should have either children or a label prop'
);
warn(
props.isDisabled && props.onPress,
'CustomComponent: onPress will not be called when isDisabled is true'
);
}
// Component implementation
}
// Debug component names
function withAriaLabel(Component) {
const WrappedComponent = (props) => {
return <Component {...props} />;
};
WrappedComponent.displayName = `withAriaLabel(${getDisplayName(Component)})`;
return WrappedComponent;
}type ForwardedRef<T> = ((instance: T | null) => void) | MutableRefObject<T | null> | null;
type RefObject<T> = { readonly current: T | null };
interface DOMAttributes<T = Element> {
// Event handlers
onFocus?: (e: FocusEvent<T>) => void;
onBlur?: (e: FocusEvent<T>) => void;
onClick?: (e: MouseEvent<T>) => void;
onKeyDown?: (e: KeyboardEvent<T>) => void;
onKeyUp?: (e: KeyboardEvent<T>) => void;
// ARIA attributes
'aria-label'?: string;
'aria-labelledby'?: string;
'aria-describedby'?: string;
'aria-expanded'?: boolean;
'aria-selected'?: boolean;
'aria-checked'?: boolean;
'aria-disabled'?: boolean;
'aria-hidden'?: boolean;
'aria-pressed'?: boolean;
'aria-current'?: boolean | 'page' | 'step' | 'location' | 'date' | 'time';
// HTML attributes
id?: string;
className?: string;
role?: string;
tabIndex?: number;
style?: React.CSSProperties;
}
// Common prop patterns
interface BaseProps {
/** Whether the component is disabled */
isDisabled?: boolean;
/** CSS class name */
className?: string;
/** Inline styles */
style?: React.CSSProperties;
/** Data test ID for testing */
'data-testid'?: string;
}
interface FocusableProps extends BaseProps {
/** Auto-focus behavior */
autoFocus?: boolean;
/** Exclude from tab order */
excludeFromTabOrder?: boolean;
/** Focus event handlers */
onFocus?: (e: FocusEvent) => void;
onBlur?: (e: FocusEvent) => void;
}
interface PressableProps extends FocusableProps {
/** Press event handlers */
onPress?: (e: PressEvent) => void;
onPressStart?: (e: PressEvent) => void;
onPressEnd?: (e: PressEvent) => void;
onPressChange?: (isPressed: boolean) => void;
onPressUp?: (e: PressEvent) => void;
}Install with Tessl CLI
npx tessl i tessl/npm-react-aria