react-aria-patterns
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseReact Aria Patterns
React Aria 组件模式
Build accessible UI components using Adobe's React Aria hooks library with React 19 patterns.
使用Adobe的React Aria Hooks库结合React 19模式构建可访问的UI组件。
Overview
概述
- Building accessible buttons, links, and toggles with keyboard/screen reader support
- Implementing modal dialogs with proper focus management and trapping
- Creating autocomplete/combobox components with filtering and selection
- Building menu systems with roving tabindex and proper ARIA roles
- Implementing accessible tables, listboxes, and selection patterns
- 构建支持键盘/屏幕阅读器的可访问按钮、链接和切换控件
- 实现具备正确焦点管理和焦点捕获的模态对话框
- 创建带有过滤和选择功能的自动完成/组合框组件
- 构建具备roving tabindex和正确ARIA角色的菜单系统
- 实现可访问的表格、列表框和选择模式
Quick Reference
快速参考
useButton - Accessible Button Component
useButton - 可访问按钮组件
tsx
import { useRef } from 'react';
import { useButton, useFocusRing, mergeProps } from 'react-aria';
import type { AriaButtonProps } from 'react-aria';
function Button(props: AriaButtonProps & { className?: string }) {
const ref = useRef<HTMLButtonElement>(null);
const { focusProps, isFocusVisible } = useFocusRing();
const { buttonProps } = useButton(props, ref);
return (
<button
{...mergeProps(buttonProps, focusProps)}
ref={ref}
className={`${props.className ?? ''} ${isFocusVisible ? 'ring-2 ring-blue-500' : ''}`}
>
{props.children}
</button>
);
}tsx
import { useRef } from 'react';
import { useButton, useFocusRing, mergeProps } from 'react-aria';
import type { AriaButtonProps } from 'react-aria';
function Button(props: AriaButtonProps & { className?: string }) {
const ref = useRef<HTMLButtonElement>(null);
const { focusProps, isFocusVisible } = useFocusRing();
const { buttonProps } = useButton(props, ref);
return (
<button
{...mergeProps(buttonProps, focusProps)}
ref={ref}
className={`${props.className ?? ''} ${isFocusVisible ? 'ring-2 ring-blue-500' : ''}`}
>
{props.children}
</button>
);
}useDialog - Modal Dialog with Focus Management
useDialog - 带焦点管理的模态对话框
tsx
import { useRef } from 'react';
import { useDialog, useModalOverlay, FocusScope, mergeProps } from 'react-aria';
import { useOverlayTriggerState } from 'react-stately';
function Modal({ state, title, children }) {
const ref = useRef<HTMLDivElement>(null);
const { modalProps, underlayProps } = useModalOverlay({}, state, ref);
const { dialogProps, titleProps } = useDialog({ 'aria-label': title }, ref);
return (
<div {...underlayProps} className="fixed inset-0 z-50 bg-black/50 flex items-center justify-center">
<FocusScope contain restoreFocus autoFocus>
<div {...mergeProps(modalProps, dialogProps)} ref={ref} className="bg-white rounded-lg p-6">
<h2 {...titleProps} className="text-xl font-semibold mb-4">{title}</h2>
{children}
</div>
</FocusScope>
</div>
);
}tsx
import { useRef } from 'react';
import { useDialog, useModalOverlay, FocusScope, mergeProps } from 'react-aria';
import { useOverlayTriggerState } from 'react-stately';
function Modal({ state, title, children }) {
const ref = useRef<HTMLDivElement>(null);
const { modalProps, underlayProps } = useModalOverlay({}, state, ref);
const { dialogProps, titleProps } = useDialog({ 'aria-label': title }, ref);
return (
<div {...underlayProps} className="fixed inset-0 z-50 bg-black/50 flex items-center justify-center">
<FocusScope contain restoreFocus autoFocus>
<div {...mergeProps(modalProps, dialogProps)} ref={ref} className="bg-white rounded-lg p-6">
<h2 {...titleProps} className="text-xl font-semibold mb-4">{title}</h2>
{children}
</div>
</FocusScope>
</div>
);
}useComboBox - Accessible Autocomplete
useComboBox - 可访问自动完成组件
tsx
import { useRef } from 'react';
import { useComboBox, useFilter } from 'react-aria';
import { useComboBoxState } from 'react-stately';
function ComboBox(props) {
const { contains } = useFilter({ sensitivity: 'base' });
const state = useComboBoxState({ ...props, defaultFilter: contains });
const inputRef = useRef(null), buttonRef = useRef(null), listBoxRef = useRef(null);
const { buttonProps, inputProps, listBoxProps, labelProps } = useComboBox(
{ ...props, inputRef, buttonRef, listBoxRef }, state
);
return (
<div className="relative inline-flex flex-col">
<label {...labelProps}>{props.label}</label>
<div className="flex">
<input {...inputProps} ref={inputRef} className="border rounded-l px-3 py-2" />
<button {...buttonProps} ref={buttonRef} className="border rounded-r px-2">▼</button>
</div>
{state.isOpen && (
<ul {...listBoxProps} ref={listBoxRef} className="absolute top-full w-full border bg-white">
{[...state.collection].map((item) => (
<li key={item.key} className="px-3 py-2 hover:bg-gray-100">{item.rendered}</li>
))}
</ul>
)}
</div>
);
}tsx
import { useRef } from 'react';
import { useComboBox, useFilter } from 'react-aria';
import { useComboBoxState } from 'react-stately';
function ComboBox(props) {
const { contains } = useFilter({ sensitivity: 'base' });
const state = useComboBoxState({ ...props, defaultFilter: contains });
const inputRef = useRef(null), buttonRef = useRef(null), listBoxRef = useRef(null);
const { buttonProps, inputProps, listBoxProps, labelProps } = useComboBox(
{ ...props, inputRef, buttonRef, listBoxRef }, state
);
return (
<div className="relative inline-flex flex-col">
<label {...labelProps}>{props.label}</label>
<div className="flex">
<input {...inputProps} ref={inputRef} className="border rounded-l px-3 py-2" />
<button {...buttonProps} ref={buttonRef} className="border rounded-r px-2">▼</button>
</div>
{state.isOpen && (
<ul {...listBoxProps} ref={listBoxRef} className="absolute top-full w-full border bg-white">
{[...state.collection].map((item) => (
<li key={item.key} className="px-3 py-2 hover:bg-gray-100">{item.rendered}</li>
))}
</ul>
)}
</div>
);
}Key Decisions
关键决策
| Decision | Option A | Option B | Recommendation |
|---|---|---|---|
| Hook vs Component | | | Hooks for control, Components for speed |
| Focus Management | Manual | | FocusScope - trapping, restore, auto-focus |
| Virtual Lists | Native scroll | | Virtualizer for lists > 100 items |
| State Management | Local useState | react-stately hooks | react-stately - designed for a11y |
| 决策项 | 选项A | 选项B | 推荐方案 |
|---|---|---|---|
| Hook vs 组件 | | react-aria-components中的 | Hooks 用于自定义控制,组件用于快速开发 |
| 焦点管理 | 手动设置 | | FocusScope - 支持焦点捕获、恢复和自动聚焦 |
| 虚拟列表 | 原生滚动 | | Virtualizer 适用于超过100条数据的列表 |
| 状态管理 | 本地useState | react-stately Hooks | react-stately - 专为可访问性设计 |
Anti-Patterns (FORBIDDEN)
反模式(禁止使用)
tsx
// NEVER use div with onClick for interactive elements
<div onClick={handleClick}>Click me</div> // Missing keyboard support!
// ALWAYS use useButton or native button
const { buttonProps } = useButton({ onPress: handleClick }, ref);
<div {...buttonProps} ref={ref}>Click me</div>
// NEVER handle focus manually for modals
useEffect(() => { modalRef.current?.focus(); }, []); // Incomplete!
// ALWAYS use FocusScope for modals/overlays
<FocusScope contain restoreFocus autoFocus>
<div role="dialog">...</div>
</FocusScope>
// NEVER forget aria-live for dynamic announcements
<div>{errorMessage}</div> // Screen readers won't announce!
// ALWAYS use aria-live for status updates
<div aria-live="polite" className="sr-only">{errorMessage}</div>
// NEVER omit label associations
<input type="text" placeholder="Email" /> // No accessible name!
// ALWAYS associate labels properly
<label {...labelProps}>Email</label>
<input {...inputProps} />tsx
// 切勿使用带onClick的div作为交互元素
<div onClick={handleClick}>Click me</div> // 缺少键盘支持!
// 务必使用useButton或原生button
const { buttonProps } = useButton({ onPress: handleClick }, ref);
<div {...buttonProps} ref={ref}>Click me</div>
// 切勿手动处理模态框的焦点
useEffect(() => { modalRef.current?.focus(); }, []); // 实现不完整!
// 务必为模态框/浮层使用FocusScope
<FocusScope contain restoreFocus autoFocus>
<div role="dialog">...</div>
</FocusScope>
// 切勿忘记为动态提示使用aria-live
<div>{errorMessage}</div> // 屏幕阅读器不会播报!
// 务必为状态更新使用aria-live
<div aria-live="polite" className="sr-only">{errorMessage}</div>
// 切勿省略标签关联
<input type="text" placeholder="Email" /> // 无可访问名称!
// 务必正确关联标签
<label {...labelProps}>Email</label>
<input {...inputProps} />Related Skills
相关技能
- - Automated accessibility testing with jest-axe and Playwright
a11y-testing - - Advanced focus patterns and keyboard navigation
focus-management - - Building accessible component libraries
design-system-starter - - Internationalization for accessible content
i18n-date-patterns
- - 使用jest-axe和Playwright进行自动化可访问性测试
a11y-testing - - 高级焦点模式和键盘导航
focus-management - - 构建可访问组件库
design-system-starter - - 可访问内容的国际化
i18n-date-patterns
Capability Details
功能细节
useButton-hook
useButton-hook
Keywords: button, useButton, press, tap, keyboard, click, onPress, focus ring
Solves:
- How to create accessible custom buttons
- Handling keyboard and pointer interactions consistently
- Focus ring visibility management with useFocusRing
关键词: button, useButton, press, tap, keyboard, click, onPress, focus ring
解决问题:
- 如何创建自定义可访问按钮
- 一致处理键盘和指针交互
- 使用useFocusRing管理焦点环可见性
useDialog-modal
useDialog-modal
Keywords: dialog, modal, useDialog, useModalOverlay, FocusScope, overlay, trap
Solves:
- Building accessible modal dialogs with proper ARIA roles
- Focus trapping within overlays using FocusScope
- Restoring focus to trigger element on close
关键词: dialog, modal, useDialog, useModalOverlay, FocusScope, overlay, trap
解决问题:
- 构建具备正确ARIA角色的可访问模态对话框
- 使用FocusScope在浮层内捕获焦点
- 关闭时将焦点恢复到触发元素
useComboBox-autocomplete
useComboBox-autocomplete
Keywords: combobox, autocomplete, useComboBox, typeahead, filter, select, dropdown
Solves:
- Accessible autocomplete/typeahead inputs with filtering
- Keyboard navigation through options (arrow keys, enter, escape)
- Screen reader announcements for selection changes
关键词: combobox, autocomplete, useComboBox, typeahead, filter, select, dropdown
解决问题:
- 带过滤功能的可访问自动完成/输入联想组件
- 选项的键盘导航(方向键、回车、ESC)
- 选择变化时的屏幕阅读器播报
focus-scope-management
focus-scope-management
Keywords: focus, FocusScope, contain, restore, autoFocus, trap, keyboard navigation
Solves:
- Trapping focus within modals and popovers (contain prop)
- Restoring focus to trigger elements on unmount (restoreFocus prop)
- Auto-focusing first focusable element (autoFocus prop)
关键词: focus, FocusScope, contain, restore, autoFocus, trap, keyboard navigation
解决问题:
- 在模态框和浮层内捕获焦点(contain属性)
- 卸载时将焦点恢复到触发元素(restoreFocus属性)
- 自动聚焦第一个可聚焦元素(autoFocus属性)