react-aria-components
npx skills add https://github.com/grahamcrackers/skills --skill react-aria-components
Agent 安装分布
Skill 文档
React Aria Components
Overview
React Aria Components is a library of unstyled, accessible components from Adobe. Each component implements W3C ARIA patterns with built-in keyboard navigation, focus management, internationalization, and screen reader support â you bring your own styles.
Setup
npm install react-aria-components
Composition Model
Every component maps 1:1 to a DOM element. Build complex widgets by composing parts:
import { Button, Dialog, DialogTrigger, Heading, Modal, ModalOverlay } from "react-aria-components";
function ConfirmDialog() {
return (
<DialogTrigger>
<Button>Delete</Button>
<ModalOverlay className="fixed inset-0 bg-black/50">
<Modal className="fixed inset-0 flex items-center justify-center">
<Dialog className="bg-white rounded-lg p-6 max-w-md">
{({ close }) => (
<>
<Heading slot="title">Confirm Delete</Heading>
<p>This action cannot be undone.</p>
<div className="flex gap-2 mt-4">
<Button onPress={close}>Cancel</Button>
<Button
onPress={() => {
handleDelete();
close();
}}
>
Delete
</Button>
</div>
</>
)}
</Dialog>
</Modal>
</ModalOverlay>
</DialogTrigger>
);
}
Styling
With Tailwind CSS
React Aria Components expose data attributes and render props for state-based styling:
import { Button } from "react-aria-components";
<Button
className="rounded-lg px-4 py-2 bg-blue-600 text-white
hover:bg-blue-700
pressed:bg-blue-800
focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2
disabled:opacity-50 disabled:cursor-not-allowed"
>
Save
</Button>;
React Aria provides Tailwind CSS variants out of the box: hover, pressed, focus-visible, disabled, selected, dragging, drop-target, entering, exiting, etc.
Install the Tailwind plugin for full support:
npm install tailwindcss-react-aria-components
/* In your CSS */
@import "tailwindcss";
@plugin "tailwindcss-react-aria-components";
With Render Props
For dynamic class names or style objects:
<Button
className={({ isPressed, isFocusVisible }) =>
`rounded-lg px-4 py-2 ${isPressed ? "bg-blue-800" : "bg-blue-600"} ${isFocusVisible ? "ring-2" : ""}`
}
>
Save
</Button>
With Vanilla CSS
Use data attributes as selectors:
.my-button {
background: var(--color-primary);
}
.my-button[data-pressed] {
background: var(--color-primary-dark);
}
.my-button[data-focus-visible] {
outline: 2px solid var(--color-focus);
outline-offset: 2px;
}
.my-button[data-disabled] {
opacity: 0.5;
}
Common Components
Button
import { Button } from "react-aria-components";
<Button onPress={() => save()} isDisabled={!isValid}>
Save
</Button>;
Use onPress instead of onClick â it handles keyboard, touch, and pointer events consistently and prevents ghost clicks on mobile.
TextField
import { TextField, Label, Input, FieldError, Text } from "react-aria-components";
<TextField isRequired>
<Label>Email</Label>
<Input type="email" className="border rounded px-3 py-2" />
<Text slot="description">We'll never share your email.</Text>
<FieldError />
</TextField>;
Select
import { Select, Label, Button, SelectValue, Popover, ListBox, ListBoxItem } from "react-aria-components";
<Select>
<Label>Country</Label>
<Button>
<SelectValue />
</Button>
<Popover>
<ListBox>
<ListBoxItem id="us">United States</ListBoxItem>
<ListBoxItem id="uk">United Kingdom</ListBoxItem>
<ListBoxItem id="ca">Canada</ListBoxItem>
</ListBox>
</Popover>
</Select>;
ComboBox
import { ComboBox, Label, Input, Button, Popover, ListBox, ListBoxItem } from "react-aria-components";
<ComboBox>
<Label>Assignee</Label>
<div className="flex">
<Input className="border rounded-l px-3 py-2" />
<Button>â¼</Button>
</div>
<Popover>
<ListBox>
{users.map((user) => (
<ListBoxItem key={user.id} id={user.id}>
{user.name}
</ListBoxItem>
))}
</ListBox>
</Popover>
</ComboBox>;
Menu
import { MenuTrigger, Button, Popover, Menu, MenuItem, Separator, Section, Header } from "react-aria-components";
<MenuTrigger>
<Button aria-label="Actions">â¯</Button>
<Popover>
<Menu onAction={(key) => handleAction(key)}>
<Section>
<Header>Edit</Header>
<MenuItem id="cut">Cut</MenuItem>
<MenuItem id="copy">Copy</MenuItem>
<MenuItem id="paste">Paste</MenuItem>
</Section>
<Separator />
<MenuItem id="delete" className="text-red-600">
Delete
</MenuItem>
</Menu>
</Popover>
</MenuTrigger>;
Tabs
import { Tabs, TabList, Tab, TabPanel } from "react-aria-components";
<Tabs>
<TabList aria-label="Settings" className="flex border-b">
<Tab id="general" className="px-4 py-2 selected:border-b-2 selected:border-blue-500">
General
</Tab>
<Tab id="security" className="px-4 py-2 selected:border-b-2 selected:border-blue-500">
Security
</Tab>
</TabList>
<TabPanel id="general">General settings...</TabPanel>
<TabPanel id="security">Security settings...</TabPanel>
</Tabs>;
Table
import { Cell, Column, Row, Table, TableBody, TableHeader, ResizableTableContainer } from "react-aria-components";
<ResizableTableContainer>
<Table aria-label="Users" selectionMode="multiple">
<TableHeader>
<Column isRowHeader>Name</Column>
<Column>Email</Column>
<Column>Role</Column>
</TableHeader>
<TableBody>
{users.map((user) => (
<Row key={user.id}>
<Cell>{user.name}</Cell>
<Cell>{user.email}</Cell>
<Cell>{user.role}</Cell>
</Row>
))}
</TableBody>
</Table>
</ResizableTableContainer>;
Collections
React Aria uses a collection API for list-based components (ListBox, Menu, Table, TagGroup, etc.):
Static
<ListBox>
<ListBoxItem id="one">Option One</ListBoxItem>
<ListBoxItem id="two">Option Two</ListBoxItem>
</ListBox>
Dynamic
<ListBox items={options}>{(item) => <ListBoxItem id={item.id}>{item.name}</ListBoxItem>}</ListBox>
Sections
<ListBox>
<Section>
<Header>Fruits</Header>
<ListBoxItem>Apple</ListBoxItem>
<ListBoxItem>Banana</ListBoxItem>
</Section>
<Section>
<Header>Vegetables</Header>
<ListBoxItem>Carrot</ListBoxItem>
<ListBoxItem>Broccoli</ListBoxItem>
</Section>
</ListBox>
Selection
Control selection on ListBox, Table, GridList, TagGroup, etc.:
const [selected, setSelected] = useState<Selection>(new Set());
<ListBox
selectionMode="multiple" // "none" | "single" | "multiple"
selectedKeys={selected}
onSelectionChange={setSelected}
>
{items.map((item) => (
<ListBoxItem key={item.id} id={item.id}>
{item.name}
</ListBoxItem>
))}
</ListBox>;
Selection is a Set<Key> or the string "all" for select-all.
Forms
React Aria Components integrate with native form validation and React 19 server actions:
import { Form, TextField, Label, Input, FieldError, Button } from "react-aria-components";
<Form
onSubmit={(e) => {
e.preventDefault(); /* handle */
}}
>
<TextField name="email" isRequired type="email">
<Label>Email</Label>
<Input />
<FieldError />
</TextField>
<Button type="submit">Submit</Button>
</Form>;
Server Validation
Display server-side errors:
const [errors, setErrors] = useState({});
<Form validationErrors={errors}>
<TextField name="email" isRequired>
<Label>Email</Label>
<Input />
<FieldError />
</TextField>
</Form>;
Overlays
Modal Dialog
<DialogTrigger>
<Button>Open</Button>
<ModalOverlay className="fixed inset-0 bg-black/50 entering:animate-in entering:fade-in exiting:animate-out exiting:fade-out">
<Modal className="fixed inset-0 flex items-center justify-center entering:animate-in entering:zoom-in-95 exiting:animate-out exiting:zoom-out-95">
<Dialog className="bg-white rounded-xl p-6 max-w-md">
{({ close }) => (
<>
<Heading slot="title">Dialog Title</Heading>
<p>Dialog content here.</p>
<Button onPress={close}>Close</Button>
</>
)}
</Dialog>
</Modal>
</ModalOverlay>
</DialogTrigger>
Popover
<DialogTrigger>
<Button>Info</Button>
<Popover className="bg-white shadow-lg rounded-lg p-4 entering:animate-in entering:fade-in exiting:animate-out exiting:fade-out">
<Dialog>
<p>Additional information here.</p>
</Dialog>
</Popover>
</DialogTrigger>
Tooltip
import { TooltipTrigger, Tooltip, Button } from "react-aria-components";
<TooltipTrigger delay={300}>
<Button>Hover me</Button>
<Tooltip className="bg-gray-900 text-white px-2 py-1 rounded text-sm">Helpful tooltip text</Tooltip>
</TooltipTrigger>;
DatePicker
import {
DatePicker,
Label,
Group,
DateInput,
DateSegment,
Button,
Popover,
Dialog,
Calendar,
CalendarGrid,
Heading,
} from "react-aria-components";
<DatePicker>
<Label>Date</Label>
<Group className="flex border rounded">
<DateInput className="flex px-2 py-1">{(segment) => <DateSegment segment={segment} />}</DateInput>
<Button>ð
</Button>
</Group>
<Popover>
<Dialog>
<Calendar>
<header className="flex items-center justify-between">
<Button slot="previous">â</Button>
<Heading />
<Button slot="next">â¶</Button>
</header>
<CalendarGrid />
</Calendar>
</Dialog>
</Popover>
</DatePicker>;
Drag and Drop
import { GridList, GridListItem, useDragAndDrop } from "react-aria-components";
function ReorderableList({ items, onReorder }) {
const { dragAndDropHooks } = useDragAndDrop({
getItems: (keys) => [...keys].map((key) => ({ "text/plain": key.toString() })),
onReorder,
});
return (
<GridList items={items} dragAndDropHooks={dragAndDropHooks} selectionMode="multiple">
{(item) => <GridListItem>{item.name}</GridListItem>}
</GridList>
);
}
Building a Design System
Wrap React Aria Components with your styling conventions:
import { Button as AriaButton, type ButtonProps as AriaButtonProps } from "react-aria-components";
import { tv } from "tailwind-variants";
const button = tv({
base: "inline-flex items-center justify-center rounded-lg font-medium transition-colors focus-visible:ring-2 focus-visible:ring-offset-2",
variants: {
variant: {
primary: "bg-blue-600 text-white hover:bg-blue-700 pressed:bg-blue-800",
secondary: "bg-gray-100 text-gray-900 hover:bg-gray-200 pressed:bg-gray-300",
danger: "bg-red-600 text-white hover:bg-red-700 pressed:bg-red-800",
},
size: {
sm: "text-sm px-3 py-1.5",
md: "text-sm px-4 py-2",
lg: "text-base px-5 py-2.5",
},
},
defaultVariants: {
variant: "primary",
size: "md",
},
});
interface ButtonProps extends AriaButtonProps {
variant?: "primary" | "secondary" | "danger";
size?: "sm" | "md" | "lg";
}
export function Button({ variant, size, className, ...props }: ButtonProps) {
return <AriaButton className={button({ variant, size, className })} {...props} />;
}
This gives you accessible primitives with your design tokens. Use tailwind-variants or cva for variant management.
Guidelines
- Use
onPressinstead ofonClickâ it handles keyboard, touch, and mouse consistently. - Every form field needs a
Labelâ React Aria handles thehtmlFor/idassociation automatically. - Use render props for state-dependent styling â
classNameandstyleaccept functions with state likeisPressed,isFocusVisible,isSelected. - Don’t add ARIA attributes manually â React Aria sets
role,aria-*, and keyboard handlers for you. - Drop down to hooks when a component doesn’t fit your use case â
useButton,useSelect, etc. give you full control. - Use the Tailwind plugin (
tailwindcss-react-aria-components) for clean state variants in class names.