React Aria offers two complementary APIs: high level React Aria Components and low level Hooks. This guide explains when to reach for hooks, and how to use them on their own or together with React Aria Components.
When to use hooks
Most applications should start with React Aria Components. They provide accessible, fully featured building blocks with minimal code, and offer extensive styling and customization options that cover the majority of use cases.
Reach for hooks when you need to go further:
- Full control over the DOM. Hooks return DOM props (event handlers, ARIA attributes, and ids). You'll render your own elements and decide the exact structure.
- Integrating with an existing system. Layer React Aria's behavior and accessibility onto components you already have, or onto a third party library.
- Rendering control beyond components. When render props and contexts aren't enough, a hook gives you direct access to the underlying behavior.
Hooks and components share the same foundations: both follow the same ARIA patterns and manage state with the same React Stately hooks. In fact, every React Aria Component is implemented on top of these hooks, so you can move between the two APIs as your needs change.
Using hooks standalone
To build a component entirely from scratch, call the hook to get the DOM props for each element, create state with the matching React Stately hook where one is needed, and spread the returned props onto your own elements. You own the rendering and styling.
import {useButton} from 'react-aria/useButton';
import {useRef} from 'react';
function Button(props) {
let ref = useRef(null);
let {buttonProps} = useButton(props, ref);
return <button {...buttonProps} ref={ref}>{props.children}</button>;
}
See useButton and useCalendar for complete from-scratch examples. The examples throughout these hook docs are available as a downloadable starter:
Combining hooks with React Aria Components
Using hooks isn't all-or-nothing. A hook returns props that you can pass to React Aria Components via context, following the Reusing children pattern. This lets you implement a component's behavior with a hook while reusing the styled sub-components you already have for the individual parts.
For example, useSelect, useComboBox, and useDateRangePicker build the overall component with a hook, then render React Aria Components for the button, popover, listbox, and calendar.
Consuming component contexts
Each React Aria component exports a corresponding context. You can build a custom implementation of a component using Hooks by consuming from the relevant context with useContextProps.
This example shows how a custom checkbox can consume CheckboxContext so it receives props from parent React Aria Components.
import {CheckboxContext, type CheckboxProps} from 'react-aria-components/Checkbox';
import {useContextProps} from 'react-aria-components/slots';
import {useToggleState} from 'react-stately/useToggleState';
import {useCheckbox} from 'react-aria/useCheckbox';
const MyCheckbox = React.forwardRef((props: CheckboxProps, ref: React.ForwardedRef<HTMLInputElement>) => {
// Merge the local props and ref with the ones provided via context.
let [mergedProps, mergedRef] = useContextProps(props, ref, CheckboxContext);
let state = useToggleState(mergedProps);
let {inputProps} = useCheckbox(mergedProps, state, mergedRef);
return <input {...inputProps} ref={mergedRef} />;
});
Since MyCheckbox consumes from CheckboxContext, it can replace the built-in Checkbox within a Table or GridList without rewriting the rest of the pattern.
<GridList>
<GridListItem>
<MyCheckbox slot="selection" />
{/* ... */}
</GridListItem>
</GridList>
Reusing children
You can also provide values for React Aria Components from a Hook-based implementation. This lets you customize the parent of a larger pattern while reusing existing child components.
This example follows the useNumberField docs, then uses Provider to send values returned by the hook to each child through its corresponding context.
import type {NumberFieldProps} from 'react-aria-components/NumberField';
import {Provider} from 'react-aria-components/slots';
import {GroupContext} from 'react-aria-components/Group';
import {InputContext} from 'react-aria-components/Input';
import {LabelContext} from 'react-aria-components/Label';
import {ButtonContext} from 'react-aria-components/Button';
import {useNumberFieldState} from 'react-stately/useNumberFieldState';
import {useNumberField} from 'react-aria/useNumberField';
import {useLocale} from 'react-aria/I18nProvider';
function CustomNumberField(props: NumberFieldProps) {
let {locale} = useLocale();
let state = useNumberFieldState({...props, locale});
let ref = useRef<HTMLInputElement>(null);
let {
labelProps,
groupProps,
inputProps,
incrementButtonProps,
decrementButtonProps
} = useNumberField(props, state, ref);
return (
<Provider
values={[
[GroupContext, groupProps],
[InputContext, {...inputProps, ref}],
[LabelContext, labelProps],
[ButtonContext, {
slots: {
increment: incrementButtonProps,
decrement: decrementButtonProps
}
}]
]}>
{props.children}
</Provider>
);
}
Because CustomNumberField provides values for Group, Input, Label, and Button, their React Aria Components implementations can be reused.
<CustomNumberField>
<Label>Width</Label>
<Group>
<Input />
<Button slot="increment">+</Button>
<Button slot="decrement">-</Button>
</Group>
</CustomNumberField>
Supporting render props
To support the render props API in a fully custom component, use useRenderProps. It resolves the className, style, and children props against the values you provide, and returns the computed values to spread onto your element.
import {useRenderProps} from 'react-aria-components';
function MyComponent(props) {
let renderProps = useRenderProps({
className: props.className,
style: props.style,
children: props.children,
defaultClassName: 'my-component',
values: {isSelected: props.isSelected}
});
return <div {...renderProps} />;
}
The values object is passed to each render props function, so consumers of MyComponent can style and render based on isSelected, just like a built-in component.