iTranslated by AI

The content below is an AI-generated translation. This is an experimental feature, and may contain errors. View original article
🐕

Building Accessible React Components with React Aria

に公開
2025/05/25

What is React Aria?

React Aria is an open-source library provided by Adobe. It offers hooks and utilities for creating accessible components in React.

By abstracting behaviors related to keyboard and mouse interactions, screen reader support, WAI-ARIA settings, and more, it allows developers to focus on the user experience without having to worry about the implementation details.

Package Installation

https://www.npmjs.com/package/react-aria

To install React Aria, use the following command:

yarn add react-aria

It is also possible to install only specific hooks.
For example, if you only need a button component, you can install it with the following command:

yarn add @react-aria/button

Installed hooks can be used as follows:

// Monopackage
import { useButton } from 'react-aria';
// Individual packages
import { useButton } from '@react-aria/button';

Basic Usage of React Aria

React Aria supports accessibility compliance and user interaction implementation through React Hooks.

A key point to note is that React Aria does not provide direct rendering. This means you must define the component's DOM structure yourself and apply the DOM properties provided by each React Aria hook to the appropriate elements.

This design gives you complete control over the DOM structure. As a result, you can control the styling yourself and freely add any necessary DOM elements.

Let's look at an example of how to use React Aria with a Button component.

import { useRef, ReactNode } from 'react';
import { useButton, AriaButtonProps } from '@react-aria/button';

interface ButtonProps extends AriaButtonProps {
  children: ReactNode;
}

const Button = (props: ButtonProps) => {
  const ref = useRef<HTMLButtonElement>(null);
  const { buttonProps } = useButton(props, ref);

  return (
    <button {...buttonProps} ref={ref}>
      {props.children}
    </button>
  );
};

// Usage example
<Button onPress={() => alert('Button pressed!')}>
  Press me
</Button>

Detailed explanations follow below.

const ref = useRef<HTMLButtonElement>(null);

Within the component, use useRef to create a reference (ref) to the DOM node.
In this case, we specify HTMLButtonElement to reference an HTML button element.

const { buttonProps } = useButton(props, ref);

Next, we call useButton, passing the props from the component and the created ref. The hook returns a buttonProps object containing the set of DOM properties that should be applied to the appropriate element.

return (
  <button {...buttonProps} ref={ref}>
    {props.children}
  </button>
);

Finally, we render the button element. Here, we apply buttonProps to the button element using the spread operator and set the ref. Then, we render the children received by the component inside the button.

At first glance, it looks very simple, but through this series of processes, accessibility support is realized.

Internal Implementation of useButton

Let's also take a brief look at how useButton realizes accessibility support.

https://github.com/adobe/react-spectrum/blob/%40adobe/react-spectrum%403.34.0/packages/%40react-aria/button/src/useButton.ts#L69-L86

This part of the process handles configurations when creating buttons using elements other than the <button> element, such as <a> or <input>. For example, you can see that it adds tabindex to enable focus movement via the Tab key and sets role="button". You can also see that properties are being configured for each specific element.

https://github.com/adobe/react-spectrum/blob/%40adobe/react-spectrum%403.34.0/packages/%40react-aria/button/src/useButton.ts#L88-L97

This process configures the handling for press events. By using the usePress hook, it sets up the logic to handle user interactions across different devices and browsers. For more details, refer to the usePress documentation.

In this way, you can expect to improve the user experience by using React Aria.

Creating Advanced Components with React Aria

Let's also explore how to create more complex components using React Aria.
As a specific example, let's look at the creation of a tab component.

Tab components require state management to keep track of the currently selected tab.
For this, we will introduce React Stately, a state management library provided by Adobe.

When implementing tab components, a useful hook called useTabListState is provided. This hook makes it possible to implement tabs simply without having to worry about state management.

Below is an example of implementing an accessible tab component by combining the useTabListState hook with React Aria's useTabList.

tab.tsx
import { useRef } from 'react';
import { useTabList } from 'react-aria';
import { Item, useTabListState } from 'react-stately';
import type { TabListStateOptions } from '@react-stately/tabs';

export const Tabs = <T extends object>(props: TabListStateOptions<T>) => {
  // Manage the tab list state with React Stately
  const state = useTabListState(props);
  // Reference to the DOM element
  const ref = useRef(null);
  // Get tab list properties with React Aria
  const { tabListProps } = useTabList(props, state, ref);
  return (
    <>
      <div {...tabListProps} ref={ref}>
        {[...state.collection].map((item) => (
          <Tab key={item.key} item={item} state={state} />
        ))}
      </div>
      <TabPanel key={state.selectedItem?.key} state={state} />
    </>
  );
};

This component renders each item in the tab list based on the state generated by useTabListState, along with the tabListProps obtained through useTabList. By expanding state.collection and rendering each tab, a dynamic tab list can be constructed.

This Tabs component, created by combining React Aria and React Stately, can be used as follows:

export const MyTab = () => {
  return (
    <Tabs aria-label="User Menu">
      <Item key="profile" title="Profile">
        Profile
      </Item>
      <Item key="settings" title="Settings">
        Settings
      </Item>
    </Tabs>
  );
};

The Tab component above provides convenient features such as navigating between tabs with arrow keys, setting a default selected tab, and handling focus with the Tab key. While implementing these features manually using state management like useState or useContext would likely be a struggle, using useTabListState makes the implementation remarkably simple.

One issue to note is that by using the Item component directly, users are forced to be aware of the internal implementation. From the user's perspective, it would be more desirable to have wrapped components available. However, at present, functionality for wrapping the Item component does not seem to be supported. This is being discussed in the following thread:

https://github.com/adobe/react-spectrum/discussions/4270

That said, according to a React Aria RFC created in February 2023, an implementation that allows wrapping the Item component is in progress, so it may become more user-friendly in the future.

https://github.com/adobe/react-spectrum/blob/main/rfcs/2023-react-aria-components.md#collections

Note that even currently, it is possible to wrap the Item component by inheriting getCollectionNode, but since this is not an officially provided method, you should verify if there are any side effects before using it.

type TabItemProps = React.ComponentProps<typeof Item>;

// Can now be used instead of Item
export const TabItem = (props: TabItemProps) => {
  return <Item {...props} />;
};
// @ts-ignore
TabItem.getCollectionNode = Item.getCollectionNode;

2023-07-01 Update: Points to note when combining React Stately and React Server Components

As a characteristic of React Server Components, it is possible to use Server Components as children of Client Components. However, when using React Aria's Item component, the child elements must also be Client Components. This is just a guess, but it is likely because the Item component uses the getCollectionNode generator function internally to traverse child elements.

Summary

Here are three key takeaways from using React Aria:

  • Accessible components can be easily implemented.
  • Integration costs are low because it can be introduced on a component-by-component basis.
  • It is best to use it in combination with React Stately when state management is required.

However, there is a certain learning cost involved in understanding the React Aria and React Stately APIs. Please consider introducing them after taking such costs into account.

Let's use React Aria to easily develop accessible web applications!

References

https://react-spectrum.adobe.com/react-aria/getting-started.html
https://react-spectrum.adobe.com/react-aria/useTabList.html
https://react-spectrum.adobe.com/react-stately/useTabListState.html

GitHubで編集を提案

Discussion