React Aria Components
React Aria Components is a library of unstyled components built on top of the React Aria hooks. It provides a simpler way to build accessible components with custom styles, while offering the flexibility to drop down to hooks for even more customizability where needed.
What is React Aria Components?#
React Aria Components is a new library of unstyled components implementing ARIA patterns, built on top of the existing React Aria hooks. It provides components for common UI patterns, with accessibility, internationalization, interactions, and behavior built in, allowing you to focus on your unique design and styling rather than re-building these challenging aspects. React Aria has been meticulously tested across a wide variety of devices, interaction modalities, and assistive technologies to ensure the best experience possible for all users.
Compared with the React Aria hooks, the components provide a default DOM structure and styling API, and abstract away the glue code necessary to connect the hooks together. The components and hooks also work together, allowing them to be mixed and matched depending on the level of customization required. Eventually, React Aria Components will be recommended as a starting point when building a new component, dropping down to hooks only when additional flexibility is needed. Both hooks and components will continue to be maintained and developed going forward.
Status#
React Aria Components is currently in beta. This means most APIs are stable but some changes may still occur, and there are some known bugs. That said, it is based on a solid and battle-tested foundation in React Aria, and we would love for you to try it out and give us feedback! Please report issues and feature requests on GitHub.
Installation#
React Aria Components can be installed using a package manager like npm or yarn.
yarn add react-aria-components
All components are available in this one package for ease of dependency management.
Implementing a component#
Once installed, you can import and render the components you need. Each component may include several parts, as documented on the corresponding component page. The API is designed around composition, where each component generally has a 1:1 relationship with a single DOM element. This makes it easy to style every element, and control the layout and DOM order as needed to implement your design.
This example renders a custom Select.
import {Button, Item, Label, ListBox, Popover, Select, SelectValue} from 'react-aria-components';
<Select>
<Label>Favorite Animal</Label>
<Button>
<SelectValue />
<span aria-hidden="true">▼</span>
</Button>
<Popover>
<ListBox>
<Item>Cat</Item>
<Item>Dog</Item>
<Item>Kangaroo</Item>
</ListBox>
</Popover>
</Select>
import {
Button,
Item,
Label,
ListBox,
Popover,
Select,
SelectValue
} from 'react-aria-components';
<Select>
<Label>Favorite Animal</Label>
<Button>
<SelectValue />
<span aria-hidden="true">▼</span>
</Button>
<Popover>
<ListBox>
<Item>Cat</Item>
<Item>Dog</Item>
<Item>Kangaroo</Item>
</ListBox>
</Popover>
</Select>
import {
Button,
Item,
Label,
ListBox,
Popover,
Select,
SelectValue
} from 'react-aria-components';
<Select>
<Label>
Favorite Animal
</Label>
<Button>
<SelectValue />
<span aria-hidden="true">
▼
</span>
</Button>
<Popover>
<ListBox>
<Item>Cat</Item>
<Item>Dog</Item>
<Item>
Kangaroo
</Item>
</ListBox>
</Popover>
</Select>
Show CSS
.react-aria-Select {
--border-color: var(--spectrum-alias-border-color);
--border-color-disabled: var(--spectrum-alias-border-color-disabled);
--text-color: var(--spectrum-alias-text-color);
--text-color-disabled: var(--spectrum-alias-text-color-disabled);
--focus-ring-color: slateblue;
.react-aria-Button {
color: var(--text-color);
background: var(--spectrum-global-color-gray-50);
border: 1px solid var(--border-color);
box-shadow: 0 1px 2px rgba(0 0 0 / 0.1);
border-radius: 6px;
appearance: none;
vertical-align: middle;
font-size: 1.072rem;
padding: 0.286rem 0.286rem 0.286rem 0.571rem;
margin: 0;
outline: none;
display: flex;
align-items: center;
max-width: 250px;
&[data-focus-visible] {
border-color: var(--focus-ring-color);
box-shadow: 0 0 0 1px var(--focus-ring-color);
}
&[data-pressed] {
background: var(--spectrum-global-color-gray-150);
}
&[data-disabled] {
border-color: var(--border-color-disabled);
color: var(--text-color-disabled);
& span[aria-hidden] {
background: var(--border-color-disabled);
}
.react-aria-SelectValue {
&[data-placeholder] {
color: var(--text-color-disabled);
}
}
}
}
.react-aria-SelectValue {
&[data-placeholder] {
font-style: italic;
color: var(--spectrum-global-color-gray-700);
}
& [slot=description] {
display: none;
}
}
& span[aria-hidden] {
width: 1.5rem;
line-height: 1.375rem;
margin-left: 1rem;
padding: 1px;
background: slateblue;
color: white;
border-radius: 4px;
font-size: 0.857rem;
}
[slot=description] {
font-size: 12px;
}
[slot=errorMessage] {
font-size: 12px;
color: var(--spectrum-global-color-red-600);
}
}
.react-aria-ListBox {
--highlight-background: slateblue;
--highlight-foreground: white;
--text-color: var(--spectrum-alias-text-color);
--text-color-disabled: var(--spectrum-alias-text-color-disabled);
max-height: inherit;
box-sizing: border-box;
overflow: auto;
padding: 2px;
outline: none;
.react-aria-Section:not(:first-child) {
margin-top: 12px;
}
.react-aria-Header {
font-size: 1.143rem;
font-weight: bold;
padding: 0 0.571rem 0 1.571rem;
}
.react-aria-Item {
margin: 2px;
padding: 0.286rem 0.571rem 0.286rem 1.571rem;
border-radius: 6px;
outline: none;
cursor: default;
color: var(--text-color);
font-size: 1.072rem;
position: relative;
display: flex;
flex-direction: column;
&[data-selected] {
font-weight: 600;
&::before {
content: '✓';
content: '✓' / '';
alt: ' ';
position: absolute;
top: 4px;
left: 4px;
}
}
&[data-focused],
&[data-pressed] {
background: var(--highlight-background);
color: var(--highlight-foreground);
}
&[data-disabled] {
color: var(--text-color-disabled);
}
[slot=label] {
font-weight: bold;
}
[slot=description] {
font-size: small;
}
}
}
.react-aria-Popover {
--background-color: var(--page-background);
--border-color: var(--spectrum-global-color-gray-400);
border: 1px solid var(--border-color);
min-width: var(--trigger-width);
max-width: 250px;
box-sizing: border-box;
box-shadow: 0 8px 20px rgba(0 0 0 / 0.1);
border-radius: 6px;
background: var(--background-color);
outline: none;
&[data-placement=top] {
--origin: translateY(8px);
}
&[data-placement=bottom] {
--origin: translateY(-8px);
}
&[data-entering] {
animation: slide 200ms;
}
&[data-exiting] {
animation: slide 200ms reverse ease-in;
}
}
@keyframes slide {
from {
transform: var(--origin);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
@media (forced-colors: active) {
.react-aria-Select {
--border-color: ButtonBorder;
--border-color-disabled: GrayText;
--text-color: ButtonText;
--text-color-disabled: GrayText;
--focus-ring-color: Highlight;
.react-aria-Button[data-disabled] span[aria-hidden] {
background: transparent;
}
}
.react-aria-ListBox {
forced-color-adjust: none;
--highlight-background: Highlight;
--highlight-foreground: HighlightText;
--border-color: ButtonBorder;
--background-color: ButtonFace;
--text-color: ButtonText;
--text-color-disabled: GrayText;
}
}
.react-aria-Select {
--border-color: var(--spectrum-alias-border-color);
--border-color-disabled: var(--spectrum-alias-border-color-disabled);
--text-color: var(--spectrum-alias-text-color);
--text-color-disabled: var(--spectrum-alias-text-color-disabled);
--focus-ring-color: slateblue;
.react-aria-Button {
color: var(--text-color);
background: var(--spectrum-global-color-gray-50);
border: 1px solid var(--border-color);
box-shadow: 0 1px 2px rgba(0 0 0 / 0.1);
border-radius: 6px;
appearance: none;
vertical-align: middle;
font-size: 1.072rem;
padding: 0.286rem 0.286rem 0.286rem 0.571rem;
margin: 0;
outline: none;
display: flex;
align-items: center;
max-width: 250px;
&[data-focus-visible] {
border-color: var(--focus-ring-color);
box-shadow: 0 0 0 1px var(--focus-ring-color);
}
&[data-pressed] {
background: var(--spectrum-global-color-gray-150);
}
&[data-disabled] {
border-color: var(--border-color-disabled);
color: var(--text-color-disabled);
& span[aria-hidden] {
background: var(--border-color-disabled);
}
.react-aria-SelectValue {
&[data-placeholder] {
color: var(--text-color-disabled);
}
}
}
}
.react-aria-SelectValue {
&[data-placeholder] {
font-style: italic;
color: var(--spectrum-global-color-gray-700);
}
& [slot=description] {
display: none;
}
}
& span[aria-hidden] {
width: 1.5rem;
line-height: 1.375rem;
margin-left: 1rem;
padding: 1px;
background: slateblue;
color: white;
border-radius: 4px;
font-size: 0.857rem;
}
[slot=description] {
font-size: 12px;
}
[slot=errorMessage] {
font-size: 12px;
color: var(--spectrum-global-color-red-600);
}
}
.react-aria-ListBox {
--highlight-background: slateblue;
--highlight-foreground: white;
--text-color: var(--spectrum-alias-text-color);
--text-color-disabled: var(--spectrum-alias-text-color-disabled);
max-height: inherit;
box-sizing: border-box;
overflow: auto;
padding: 2px;
outline: none;
.react-aria-Section:not(:first-child) {
margin-top: 12px;
}
.react-aria-Header {
font-size: 1.143rem;
font-weight: bold;
padding: 0 0.571rem 0 1.571rem;
}
.react-aria-Item {
margin: 2px;
padding: 0.286rem 0.571rem 0.286rem 1.571rem;
border-radius: 6px;
outline: none;
cursor: default;
color: var(--text-color);
font-size: 1.072rem;
position: relative;
display: flex;
flex-direction: column;
&[data-selected] {
font-weight: 600;
&::before {
content: '✓';
content: '✓' / '';
alt: ' ';
position: absolute;
top: 4px;
left: 4px;
}
}
&[data-focused],
&[data-pressed] {
background: var(--highlight-background);
color: var(--highlight-foreground);
}
&[data-disabled] {
color: var(--text-color-disabled);
}
[slot=label] {
font-weight: bold;
}
[slot=description] {
font-size: small;
}
}
}
.react-aria-Popover {
--background-color: var(--page-background);
--border-color: var(--spectrum-global-color-gray-400);
border: 1px solid var(--border-color);
min-width: var(--trigger-width);
max-width: 250px;
box-sizing: border-box;
box-shadow: 0 8px 20px rgba(0 0 0 / 0.1);
border-radius: 6px;
background: var(--background-color);
outline: none;
&[data-placement=top] {
--origin: translateY(8px);
}
&[data-placement=bottom] {
--origin: translateY(-8px);
}
&[data-entering] {
animation: slide 200ms;
}
&[data-exiting] {
animation: slide 200ms reverse ease-in;
}
}
@keyframes slide {
from {
transform: var(--origin);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
@media (forced-colors: active) {
.react-aria-Select {
--border-color: ButtonBorder;
--border-color-disabled: GrayText;
--text-color: ButtonText;
--text-color-disabled: GrayText;
--focus-ring-color: Highlight;
.react-aria-Button[data-disabled] span[aria-hidden] {
background: transparent;
}
}
.react-aria-ListBox {
forced-color-adjust: none;
--highlight-background: Highlight;
--highlight-foreground: HighlightText;
--border-color: ButtonBorder;
--background-color: ButtonFace;
--text-color: ButtonText;
--text-color-disabled: GrayText;
}
}
.react-aria-Select {
--border-color: var(--spectrum-alias-border-color);
--border-color-disabled: var(--spectrum-alias-border-color-disabled);
--text-color: var(--spectrum-alias-text-color);
--text-color-disabled: var(--spectrum-alias-text-color-disabled);
--focus-ring-color: slateblue;
.react-aria-Button {
color: var(--text-color);
background: var(--spectrum-global-color-gray-50);
border: 1px solid var(--border-color);
box-shadow: 0 1px 2px rgba(0 0 0 / 0.1);
border-radius: 6px;
appearance: none;
vertical-align: middle;
font-size: 1.072rem;
padding: 0.286rem 0.286rem 0.286rem 0.571rem;
margin: 0;
outline: none;
display: flex;
align-items: center;
max-width: 250px;
&[data-focus-visible] {
border-color: var(--focus-ring-color);
box-shadow: 0 0 0 1px var(--focus-ring-color);
}
&[data-pressed] {
background: var(--spectrum-global-color-gray-150);
}
&[data-disabled] {
border-color: var(--border-color-disabled);
color: var(--text-color-disabled);
& span[aria-hidden] {
background: var(--border-color-disabled);
}
.react-aria-SelectValue {
&[data-placeholder] {
color: var(--text-color-disabled);
}
}
}
}
.react-aria-SelectValue {
&[data-placeholder] {
font-style: italic;
color: var(--spectrum-global-color-gray-700);
}
& [slot=description] {
display: none;
}
}
& span[aria-hidden] {
width: 1.5rem;
line-height: 1.375rem;
margin-left: 1rem;
padding: 1px;
background: slateblue;
color: white;
border-radius: 4px;
font-size: 0.857rem;
}
[slot=description] {
font-size: 12px;
}
[slot=errorMessage] {
font-size: 12px;
color: var(--spectrum-global-color-red-600);
}
}
.react-aria-ListBox {
--highlight-background: slateblue;
--highlight-foreground: white;
--text-color: var(--spectrum-alias-text-color);
--text-color-disabled: var(--spectrum-alias-text-color-disabled);
max-height: inherit;
box-sizing: border-box;
overflow: auto;
padding: 2px;
outline: none;
.react-aria-Section:not(:first-child) {
margin-top: 12px;
}
.react-aria-Header {
font-size: 1.143rem;
font-weight: bold;
padding: 0 0.571rem 0 1.571rem;
}
.react-aria-Item {
margin: 2px;
padding: 0.286rem 0.571rem 0.286rem 1.571rem;
border-radius: 6px;
outline: none;
cursor: default;
color: var(--text-color);
font-size: 1.072rem;
position: relative;
display: flex;
flex-direction: column;
&[data-selected] {
font-weight: 600;
&::before {
content: '✓';
content: '✓' / '';
alt: ' ';
position: absolute;
top: 4px;
left: 4px;
}
}
&[data-focused],
&[data-pressed] {
background: var(--highlight-background);
color: var(--highlight-foreground);
}
&[data-disabled] {
color: var(--text-color-disabled);
}
[slot=label] {
font-weight: bold;
}
[slot=description] {
font-size: small;
}
}
}
.react-aria-Popover {
--background-color: var(--page-background);
--border-color: var(--spectrum-global-color-gray-400);
border: 1px solid var(--border-color);
min-width: var(--trigger-width);
max-width: 250px;
box-sizing: border-box;
box-shadow: 0 8px 20px rgba(0 0 0 / 0.1);
border-radius: 6px;
background: var(--background-color);
outline: none;
&[data-placement=top] {
--origin: translateY(8px);
}
&[data-placement=bottom] {
--origin: translateY(-8px);
}
&[data-entering] {
animation: slide 200ms;
}
&[data-exiting] {
animation: slide 200ms reverse ease-in;
}
}
@keyframes slide {
from {
transform: var(--origin);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
@media (forced-colors: active) {
.react-aria-Select {
--border-color: ButtonBorder;
--border-color-disabled: GrayText;
--text-color: ButtonText;
--text-color-disabled: GrayText;
--focus-ring-color: Highlight;
.react-aria-Button[data-disabled] span[aria-hidden] {
background: transparent;
}
}
.react-aria-ListBox {
forced-color-adjust: none;
--highlight-background: Highlight;
--highlight-foreground: HighlightText;
--border-color: ButtonBorder;
--background-color: ButtonFace;
--text-color: ButtonText;
--text-color-disabled: GrayText;
}
}
Styling#
React Aria Components do not include any styles by default, allowing you to build custom designs to fit your application or design system. It works with any styling solution, including vanilla CSS, Tailwind CSS, CSS-in-JS, etc. See the styling guide for full details.
Examples#
The documentation for each component includes many examples styled using vanilla CSS and the default class names. We also have an example of many of the components using Tailwind CSS. You can find the code in the repo, as well as a live demo.