React Aria (Adobe) accessible component patterns for building WCAG-compliant interactive UI with hooks. Use when implementing buttons, dialogs, comboboxes, menus, and other accessible components in React applications.
Generates accessible React components using React Aria hooks for WCAG-compliant UI patterns.
/plugin marketplace add yonatangross/skillforge-claude-plugin/plugin install skillforge-complete@skillforgeThis skill is limited to using the following tools:
checklists/react-aria-checklist.mdexamples/react-aria-examples.mdreferences/react-aria-hooks.mdtemplates/accessible-component-template.tsxBuild accessible UI components using Adobe's React Aria hooks library with React 19 patterns.
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>
);
}
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>
);
}
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>
);
}
| Decision | Option A | Option B | Recommendation |
|---|---|---|---|
| Hook vs Component | useButton hooks | Button from react-aria-components | Hooks for control, Components for speed |
| Focus Management | Manual tabIndex | FocusScope component | FocusScope - trapping, restore, auto-focus |
| Virtual Lists | Native scroll | useVirtualizer + useListBox | Virtualizer for lists > 100 items |
| State Management | Local useState | react-stately hooks | react-stately - designed for a11y |
// 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} />
a11y-testing - Automated accessibility testing with jest-axe and Playwrightfocus-management - Advanced focus patterns and keyboard navigationdesign-system-starter - Building accessible component librariesi18n-date-patterns - Internationalization for accessible contentKeywords: button, useButton, press, tap, keyboard, click, onPress, focus ring Solves:
Keywords: dialog, modal, useDialog, useModalOverlay, FocusScope, overlay, trap Solves:
Keywords: combobox, autocomplete, useComboBox, typeahead, filter, select, dropdown Solves:
Keywords: focus, FocusScope, contain, restore, autoFocus, trap, keyboard navigation Solves:
Activates when the user asks about AI prompts, needs prompt templates, wants to search for prompts, or mentions prompts.chat. Use for discovering, retrieving, and improving prompts.
Activates when the user asks about Agent Skills, wants to find reusable AI capabilities, needs to install skills, or mentions skills for Claude. Use for discovering, retrieving, and installing skills.
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.