Fix: React Aria Not Working — Components Not Rendering, ARIA Attributes Missing, or Styling Conflicts
Quick Answer
How to fix React Aria and React Aria Components issues — hooks vs components API, styling with Tailwind CSS, custom components, collections pattern, forms, and accessibility compliance.
The Problem
A React Aria component renders but has no visible styles:
import { Button } from 'react-aria-components';
function App() {
return <Button>Click me</Button>;
// Renders a plain unstyled button — no visual feedback
}Or a Select component doesn’t open its popover:
import { Select, SelectValue, Popover, ListBox, ListBoxItem } from 'react-aria-components';
<Select>
<SelectValue />
<Popover>
<ListBox>
<ListBoxItem>Option 1</ListBoxItem>
</ListBox>
</Popover>
</Select>
// Click does nothing — popover never appearsOr custom components lose keyboard navigation:
Tab key doesn't focus the component, arrow keys don't workWhy This Happens
React Aria is Adobe’s accessibility-first component library. It comes in two forms — hooks (react-aria) and pre-built components (react-aria-components):
- React Aria Components are unstyled — like Radix, they provide behavior and ARIA attributes without CSS. Every visual aspect (colors, borders, padding) must be added by you. Without styles, components are functional but invisible.
- Components have required children —
SelectneedsButton,Popover,ListBox, andListBoxItemas children in the correct structure. Missing a required child breaks the interaction chain. - Render props provide state for styling — React Aria Components use render props to expose state like
isPressed,isFocused,isSelected. You use these to apply conditional styles. - The hooks API requires manual ARIA wiring — if using hooks (
useButton,useSelect), you must spread the returned props onto DOM elements. Missing a spread loses keyboard handling and ARIA attributes.
Fix 1: Basic Components with Tailwind CSS
npm install react-aria-components
# Or for hooks API: npm install react-aria react-stately// Button with render props
import { Button } from 'react-aria-components';
function StyledButton({ children, ...props }) {
return (
<Button
{...props}
className={({ isHovered, isPressed, isFocusVisible, isDisabled }) =>
`inline-flex items-center justify-center px-4 py-2 rounded-lg font-medium transition-colors
${isDisabled ? 'opacity-50 cursor-not-allowed bg-gray-200 text-gray-500' :
isPressed ? 'bg-blue-700 text-white scale-95' :
isHovered ? 'bg-blue-600 text-white' :
'bg-blue-500 text-white'}
${isFocusVisible ? 'ring-2 ring-blue-400 ring-offset-2' : ''}`
}
>
{children}
</Button>
);
}
// TextField
import { TextField, Label, Input, FieldError, Text } from 'react-aria-components';
function StyledTextField({ label, description, errorMessage, ...props }) {
return (
<TextField {...props} className="flex flex-col gap-1">
<Label className="text-sm font-medium text-gray-700">{label}</Label>
<Input className={({ isFocused, isInvalid }) =>
`px-3 py-2 rounded-lg border outline-none transition-colors
${isInvalid ? 'border-red-500 bg-red-50' :
isFocused ? 'border-blue-500 ring-2 ring-blue-200' :
'border-gray-300'}`
} />
{description && <Text slot="description" className="text-xs text-gray-500">{description}</Text>}
<FieldError className="text-xs text-red-500">{errorMessage}</FieldError>
</TextField>
);
}Fix 2: Select / Dropdown
import {
Select, SelectValue, Button, Label, Popover, ListBox, ListBoxItem,
} from 'react-aria-components';
interface Option {
id: string;
name: string;
}
function StyledSelect({ label, options, ...props }: {
label: string;
options: Option[];
}) {
return (
<Select {...props} className="flex flex-col gap-1">
<Label className="text-sm font-medium text-gray-700">{label}</Label>
<Button className={({ isFocused, isOpen }) =>
`flex items-center justify-between px-3 py-2 rounded-lg border outline-none transition-colors
${isOpen ? 'border-blue-500 ring-2 ring-blue-200' :
isFocused ? 'border-blue-400' :
'border-gray-300'}
bg-white`
}>
<SelectValue className="truncate" />
<span aria-hidden="true">▾</span>
</Button>
<Popover className="w-[--trigger-width] bg-white rounded-lg shadow-xl border border-gray-200 overflow-hidden">
<ListBox className="p-1 max-h-60 overflow-y-auto outline-none">
{options.map(option => (
<ListBoxItem
key={option.id}
id={option.id}
textValue={option.name}
className={({ isFocused, isSelected }) =>
`px-3 py-2 rounded-md outline-none cursor-pointer
${isSelected ? 'bg-blue-500 text-white' :
isFocused ? 'bg-blue-50 text-blue-900' :
'text-gray-900'}`
}
>
{option.name}
</ListBoxItem>
))}
</ListBox>
</Popover>
</Select>
);
}
// Usage
<StyledSelect
label="Framework"
options={[
{ id: 'react', name: 'React' },
{ id: 'vue', name: 'Vue' },
{ id: 'svelte', name: 'Svelte' },
]}
onSelectionChange={(key) => console.log('Selected:', key)}
/>Fix 3: Dialog / Modal
import {
DialogTrigger, Button, Modal, ModalOverlay, Dialog, Heading,
} from 'react-aria-components';
function ConfirmDialog() {
return (
<DialogTrigger>
<Button className="px-4 py-2 bg-red-500 text-white rounded-lg hover:bg-red-600">
Delete
</Button>
<ModalOverlay className={({ isEntering, isExiting }) =>
`fixed inset-0 z-50 flex items-center justify-center bg-black/50
${isEntering ? 'animate-in fade-in duration-200' : ''}
${isExiting ? 'animate-out fade-out duration-150' : ''}`
}>
<Modal className={({ isEntering, isExiting }) =>
`w-full max-w-md bg-white rounded-xl shadow-2xl p-6
${isEntering ? 'animate-in zoom-in-95 duration-200' : ''}
${isExiting ? 'animate-out zoom-out-95 duration-150' : ''}`
}>
<Dialog className="outline-none">
{({ close }) => (
<>
<Heading slot="title" className="text-lg font-bold mb-2">
Confirm Deletion
</Heading>
<p className="text-gray-600 mb-6">
Are you sure? This action cannot be undone.
</p>
<div className="flex gap-3 justify-end">
<Button
onPress={close}
className="px-4 py-2 rounded-lg bg-gray-100 hover:bg-gray-200"
>
Cancel
</Button>
<Button
onPress={() => { handleDelete(); close(); }}
className="px-4 py-2 rounded-lg bg-red-500 text-white hover:bg-red-600"
>
Delete
</Button>
</div>
</>
)}
</Dialog>
</Modal>
</ModalOverlay>
</DialogTrigger>
);
}Fix 4: Table with Sorting and Selection
import {
Table, TableHeader, Column, TableBody, Row, Cell, Checkbox,
} from 'react-aria-components';
function DataTable({ data }: { data: User[] }) {
return (
<Table
aria-label="Users"
selectionMode="multiple"
sortDescriptor={{ column: 'name', direction: 'ascending' }}
onSortChange={(descriptor) => console.log('Sort:', descriptor)}
className="w-full border-collapse"
>
<TableHeader>
<Column isRowHeader allowsSorting className="text-left p-3 border-b font-medium">
Name
</Column>
<Column allowsSorting className="text-left p-3 border-b font-medium">
Email
</Column>
<Column className="text-left p-3 border-b font-medium">
Role
</Column>
</TableHeader>
<TableBody>
{data.map(user => (
<Row
key={user.id}
id={user.id}
className={({ isSelected, isFocused }) =>
`${isSelected ? 'bg-blue-50' : isFocused ? 'bg-gray-50' : ''}
border-b transition-colors`
}
>
<Cell className="p-3">{user.name}</Cell>
<Cell className="p-3">{user.email}</Cell>
<Cell className="p-3">{user.role}</Cell>
</Row>
))}
</TableBody>
</Table>
);
}Fix 5: Hooks API (Maximum Control)
// When you need full control over the DOM structure
import { useButton } from 'react-aria';
import { useRef } from 'react';
function CustomButton({ onPress, children }) {
const ref = useRef<HTMLButtonElement>(null);
const { buttonProps, isPressed } = useButton({ onPress }, ref);
return (
<button
{...buttonProps} // Spreads onClick, onKeyDown, role, tabIndex, etc.
ref={ref}
className={`px-4 py-2 rounded ${isPressed ? 'bg-blue-700' : 'bg-blue-500'} text-white`}
>
{children}
</button>
);
}
// useComboBox — autocomplete input
import { useComboBox, useFilter } from 'react-aria';
import { useComboBoxState } from 'react-stately';
function AutoComplete({ items, label }) {
const { contains } = useFilter({ sensitivity: 'base' });
const state = useComboBoxState({ items, defaultFilter: contains });
const inputRef = useRef(null);
const listBoxRef = useRef(null);
const popoverRef = useRef(null);
const { inputProps, listBoxProps, labelProps } = useComboBox(
{ inputRef, listBoxRef, popoverRef, label },
state,
);
return (
<div>
<label {...labelProps}>{label}</label>
<input {...inputProps} ref={inputRef} className="border rounded px-3 py-2" />
{state.isOpen && (
<div ref={popoverRef} className="absolute bg-white shadow-lg rounded mt-1">
<ul {...listBoxProps} ref={listBoxRef}>
{[...state.collection].map(item => (
<li key={item.key} className="px-3 py-2 hover:bg-gray-100 cursor-pointer">
{item.rendered}
</li>
))}
</ul>
</div>
)}
</div>
);
}Fix 6: Form Validation
import { Form, TextField, Label, Input, FieldError, Button } from 'react-aria-components';
function ContactForm() {
return (
<Form
onSubmit={(e) => {
e.preventDefault();
const data = Object.fromEntries(new FormData(e.currentTarget));
console.log(data);
}}
validationBehavior="native"
className="flex flex-col gap-4 max-w-md"
>
<TextField name="name" isRequired className="flex flex-col gap-1">
<Label className="text-sm font-medium">Name</Label>
<Input className="px-3 py-2 border rounded-lg" />
<FieldError className="text-xs text-red-500" />
</TextField>
<TextField name="email" type="email" isRequired className="flex flex-col gap-1">
<Label className="text-sm font-medium">Email</Label>
<Input className="px-3 py-2 border rounded-lg" />
<FieldError className="text-xs text-red-500" />
</TextField>
<Button type="submit" className="px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600">
Submit
</Button>
</Form>
);
}Still Not Working?
Component renders but is invisible — React Aria Components ship with zero CSS. Add styles via className (string or function for state-based styles). The render prop pattern className={({ isHovered }) => ...} gives you access to component state for dynamic styling.
Select/Popover doesn’t open — check the component tree structure. Select needs Button (trigger), Popover (container), ListBox, and ListBoxItem in the correct hierarchy. Missing the Button child means there’s no trigger element to open the popover.
Keyboard navigation doesn’t work — don’t add custom onClick or onKeyDown handlers that call e.stopPropagation(). React Aria manages keyboard events internally. Use onPress instead of onClick, and onSelectionChange instead of custom click handlers on list items.
TypeScript errors with render props — the className function receives state props specific to each component. Button provides isHovered, isPressed, isFocusVisible, isDisabled. ListBoxItem provides isSelected, isFocused. Check the docs for each component’s available states.
For related component library issues, see Fix: Radix UI Not Working and Fix: shadcn/ui Not Working.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: cmdk Not Working — Command Palette Not Opening, Items Not Filtering, or Keyboard Navigation Broken
How to fix cmdk command palette issues — Dialog setup, custom filtering, groups and separators, keyboard shortcuts, async search, nested pages, and integration with shadcn/ui and Tailwind.
Fix: Conform Not Working — Form Validation Not Triggering, Server Errors Missing, or Zod Schema Rejected
How to fix Conform form validation issues — useForm setup with Zod, server action integration, nested and array fields, file uploads, progressive enhancement, and Remix and Next.js usage.
Fix: i18next Not Working — Translations Missing, Language Not Switching, or Namespace Errors
How to fix i18next issues — react-i18next setup, translation file loading, namespace configuration, language detection, interpolation, pluralization, and Next.js integration.
Fix: Lingui Not Working — Messages Not Extracted, Translations Missing, or Macro Errors
How to fix Lingui.js i18n issues — setup with React, message extraction, macro compilation, ICU format, lazy loading catalogs, and Next.js integration.