PettyUI/.firecrawl/pkgpulse-reactaria-radix.md
Mats Bosson db906fd85a Fix linting config and package fields
- Replace .eslintrc.cjs with eslint.config.mjs (ESLint 9 flat config)
  using direct eslint-plugin-solid + @typescript-eslint/parser approach
- Add @typescript-eslint/parser to root devDependencies
- Add main/module/types top-level fields to packages/core/package.json
- Add resolve.conditions to packages/core/vite.config.ts
- Create packages/core/tsconfig.test.json for test type-checking
- Remove empty paths:{} from packages/core/tsconfig.json
2026-03-29 02:35:57 +07:00

377 lines
14 KiB
Markdown

[Skip to main content](https://www.pkgpulse.com/blog/react-aria-vs-radix-primitives-2026#main-content)
Accessibility litigation against web applications increased by 30% year-over-year in 2025. Building accessible UI has gone from "nice to have" to legal requirement for many organizations — and the right headless component library can be the difference between compliance and a lawsuit. React Aria (Adobe) and Radix Primitives are the two dominant choices, and they differ profoundly in philosophy.
## [TL;DR](https://www.pkgpulse.com/blog/react-aria-vs-radix-primitives-2026\#tldr)
**Radix Primitives** is the right default for most React applications — pragmatic, well-documented, widely adopted (60K+ stars), and accessible enough for the vast majority of use cases. **React Aria** is the right choice when accessibility is a primary constraint, WCAG compliance is required by contract, or you need the strictest ARIA pattern implementation available. Both are significantly better than building accessible components from scratch.
## [Key Takeaways](https://www.pkgpulse.com/blog/react-aria-vs-radix-primitives-2026\#key-takeaways)
- Radix Primitives: ~3.5M weekly downloads across packages, 60K+ GitHub stars
- React Aria Components: ~260K weekly downloads for `react-aria-components`
- Radix: 28 main components; React Aria: 43+ components
- React Aria is built by Adobe (accessibility specialists), used in Adobe Spectrum
- Radix is the foundation of Shadcn UI — the most popular component library of 2025
- React Aria strictly follows WCAG 2.1 patterns; Radix prioritizes pragmatic DX with good accessibility
- Both ship behavior/logic only — you provide the styles (headless architecture)
## [The Headless Component Model](https://www.pkgpulse.com/blog/react-aria-vs-radix-primitives-2026\#the-headless-component-model)
Both libraries are "headless" — they provide behavior, accessibility, and keyboard interactions, but **no visual styling**. You bring your own CSS (Tailwind, CSS Modules, etc.).
This is the right architecture for component libraries: it separates behavior concerns from visual concerns, making the library usable in any design system.
## [Radix Primitives](https://www.pkgpulse.com/blog/react-aria-vs-radix-primitives-2026\#radix-primitives)
**Packages**: `@radix-ui/react-*` (28+ packages)
**Downloads**: ~3.5M weekly (across all packages)
**GitHub stars**: 16K (primitives repo), 60K+ (Radix UI org)
**Created by**: WorkOS
Radix is the most popular headless component library in the React ecosystem, largely because it powers Shadcn UI.
### [Installation](https://www.pkgpulse.com/blog/react-aria-vs-radix-primitives-2026\#installation)
```bash
# Install individual primitives as needed
npm install @radix-ui/react-dialog
npm install @radix-ui/react-dropdown-menu
npm install @radix-ui/react-select
npm install @radix-ui/react-tooltip
```
### [Usage Pattern](https://www.pkgpulse.com/blog/react-aria-vs-radix-primitives-2026\#usage-pattern)
Radix uses a compound component pattern:
```tsx
import * as Dialog from '@radix-ui/react-dialog';
function DeleteConfirmDialog({ onConfirm }: { onConfirm: () => void }) {
return (
<Dialog.Root>
<Dialog.Trigger asChild>
<button className="btn-danger">Delete Account</button>
</Dialog.Trigger>
<Dialog.Portal>
<Dialog.Overlay className="fixed inset-0 bg-black/50 animate-fade-in" />
<Dialog.Content className="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 bg-white rounded-lg p-6 shadow-xl">
<Dialog.Title className="text-lg font-semibold">
Confirm Deletion
</Dialog.Title>
<Dialog.Description className="text-gray-600 mt-2">
This action cannot be undone. All your data will be permanently deleted.
</Dialog.Description>
<div className="flex gap-3 mt-6">
<Dialog.Close asChild>
<button className="btn-secondary">Cancel</button>
</Dialog.Close>
<Dialog.Close asChild>
<button className="btn-danger" onClick={onConfirm}>
Delete
</button>
</Dialog.Close>
</div>
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
);
}
```
### [`asChild` Prop](https://www.pkgpulse.com/blog/react-aria-vs-radix-primitives-2026\#aschild-prop)
One of Radix's best DX features: `asChild` lets you apply Radix behavior to any component without adding extra DOM elements:
```tsx
import * as Tooltip from '@radix-ui/react-tooltip';
import { Link } from 'next/link'; // Or any custom component
// Without asChild: wraps in a <button>
<Tooltip.Trigger>Hover me</Tooltip.Trigger>
// With asChild: applies trigger behavior to Link directly
<Tooltip.Trigger asChild>
<Link href="/docs">Documentation</Link>
</Tooltip.Trigger>
```
### [Data Attributes for Styling](https://www.pkgpulse.com/blog/react-aria-vs-radix-primitives-2026\#data-attributes-for-styling)
Radix exposes state via `data-` attributes, making CSS targeting clean:
```css
/* Style based on component state */
[data-state="open"] > .trigger-icon {
transform: rotate(180deg);
}
[data-highlighted] {
background-color: var(--color-accent);
}
[data-disabled] {
opacity: 0.5;
cursor: not-allowed;
}
```
### [Available Components (2026)](https://www.pkgpulse.com/blog/react-aria-vs-radix-primitives-2026\#available-components-2026)
Accordion, Alert Dialog, Aspect Ratio, Avatar, Checkbox, Collapsible, Context Menu, Dialog, Dropdown Menu, Form, Hover Card, Label, Menubar, Navigation Menu, Popover, Progress, Radio Group, Scroll Area, Select, Separator, Slider, Switch, Tabs, Toast, Toggle, Toggle Group, Toolbar, Tooltip.
## [React Aria](https://www.pkgpulse.com/blog/react-aria-vs-radix-primitives-2026\#react-aria)
**Package**: `react-aria-components` (all-in-one), or individual `@react-aria/*` hooks
**Downloads**: ~260K weekly (`react-aria-components`)
**GitHub stars**: 13K (react-spectrum monorepo)
**Created by**: Adobe
React Aria comes from Adobe's design systems team — the same team that builds accessibility-focused products used by millions. It implements ARIA patterns from the WAI-ARIA Authoring Practices Guide more strictly than any other library.
### [Installation](https://www.pkgpulse.com/blog/react-aria-vs-radix-primitives-2026\#installation-1)
```bash
# All-in-one package (recommended for new projects)
npm install react-aria-components
# Or individual hooks for granular control
npm install @react-aria/dialog @react-aria/focus @react-stately/dialog
```
### [Component API](https://www.pkgpulse.com/blog/react-aria-vs-radix-primitives-2026\#component-api)
React Aria uses a render props pattern that gives you maximum control:
```tsx
import { Dialog, DialogTrigger, Modal, ModalOverlay, Button, Heading } from 'react-aria-components';
function DeleteConfirmDialog({ onConfirm }: { onConfirm: () => void }) {
return (
<DialogTrigger>
<Button className="btn-danger">Delete Account</Button>
<ModalOverlay className="fixed inset-0 bg-black/50 animate-fade-in">
<Modal className="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 bg-white rounded-lg p-6 shadow-xl">
<Dialog>
{({ close }) => (
<>
<Heading slot="title" className="text-lg font-semibold">
Confirm Deletion
</Heading>
<p className="text-gray-600 mt-2">
This action cannot be undone.
</p>
<div className="flex gap-3 mt-6">
<Button onPress={close} className="btn-secondary">Cancel</Button>
<Button onPress={() => { onConfirm(); close(); }} className="btn-danger">
Delete
</Button>
</div>
</>
)}
</Dialog>
</Modal>
</ModalOverlay>
</DialogTrigger>
);
}
```
### [CSS Classes with State](https://www.pkgpulse.com/blog/react-aria-vs-radix-primitives-2026\#css-classes-with-state)
React Aria uses a slightly different styling approach:
```tsx
// renderProps pattern for dynamic classes
<Button
className={({ isPressed, isFocused, isDisabled }) =>
`btn ${isPressed ? 'btn-pressed' : ''} ${isFocused ? 'btn-focused' : ''}`
}
>
Click me
</Button>
```
Or with data attributes (similar to Radix):
```css
.btn[data-pressed] { transform: scale(0.98); }
.btn[data-focused] { outline: 2px solid var(--color-focus); }
.btn[data-disabled] { opacity: 0.5; }
```
### [Advanced Accessibility Features](https://www.pkgpulse.com/blog/react-aria-vs-radix-primitives-2026\#advanced-accessibility-features)
React Aria handles edge cases that simpler libraries miss:
```tsx
// ComboBox with proper ARIA pattern
import { ComboBox, Item, Label, Input, Popover, ListBox } from 'react-aria-components';
function SearchComboBox() {
return (
<ComboBox>
<Label>Search countries</Label>
<Input />
<Popover>
<ListBox>
<Item>Afghanistan</Item>
<Item>Albania</Item>
{/* ... */}
</ListBox>
</Popover>
</ComboBox>
);
}
```
React Aria correctly handles:
- Proper `combobox` ARIA role with `aria-expanded`, `aria-haspopup`, `aria-owns`
- `aria-activedescendant` updates on keyboard navigation
- Correct focus management when popup opens/closes
- Mobile: virtual cursor navigation for screen readers on iOS/Android
- Touch: proper pointer events for touch screen accessibility
### [Hook-Level API](https://www.pkgpulse.com/blog/react-aria-vs-radix-primitives-2026\#hook-level-api)
For more control, you can use the individual hooks:
```typescript
import { useButton } from '@react-aria/button';
import { useRef } from 'react';
function CustomButton({ onPress, children }: Props) {
const ref = useRef<HTMLButtonElement>(null);
const { buttonProps } = useButton({ onPress }, ref);
return (
<button
{...buttonProps}
ref={ref}
className="custom-button"
>
{children}
</button>
);
}
```
## [Accessibility Comparison](https://www.pkgpulse.com/blog/react-aria-vs-radix-primitives-2026\#accessibility-comparison)
### [ARIA Compliance](https://www.pkgpulse.com/blog/react-aria-vs-radix-primitives-2026\#aria-compliance)
| Component | Radix | React Aria |
| --- | --- | --- |
| Dialog | WCAG AA | WCAG AAA |
| Select | WCAG AA | WCAG AAA |
| Combobox | Good | Strict |
| Date Picker | Not included | Full ARIA pattern |
| Grid | Not included | Full keyboard nav |
| Virtual cursor | Partial | Full iOS/Android |
### [Screen Reader Testing](https://www.pkgpulse.com/blog/react-aria-vs-radix-primitives-2026\#screen-reader-testing)
React Aria tests against: JAWS + Chrome, NVDA + Firefox, VoiceOver + Safari, TalkBack + Chrome, VoiceOver + iOS Safari.
Radix tests against major screen readers but with less rigor on mobile platforms.
## [Components Available (React Aria vs Radix)](https://www.pkgpulse.com/blog/react-aria-vs-radix-primitives-2026\#components-available-react-aria-vs-radix)
| Component | Radix | React Aria |
| --- | --- | --- |
| Button | No (basic) | Yes |
| Checkbox | Yes | Yes |
| Dialog/Modal | Yes | Yes |
| Dropdown Menu | Yes | Yes |
| Select | Yes | Yes |
| Combobox | No | Yes |
| Date Picker | No | Yes |
| Calendar | No | Yes |
| Color Picker | No | Yes |
| Table/Grid | No | Yes |
| Drag & Drop | No | Yes |
| File Trigger | No | Yes |
| Tag Group | No | Yes |
React Aria has significantly more components, especially for complex interactive patterns.
## [Bundle Size](https://www.pkgpulse.com/blog/react-aria-vs-radix-primitives-2026\#bundle-size)
| Package | Gzipped |
| --- | --- |
| `@radix-ui/react-dialog` | ~5.5 kB |
| `@radix-ui/react-dropdown-menu` | ~12 kB |
| `react-aria-components` (tree-shaken) | ~8-20 kB per component |
## [The Shadcn Effect](https://www.pkgpulse.com/blog/react-aria-vs-radix-primitives-2026\#the-shadcn-effect)
Radix's dominant download numbers are largely the Shadcn UI effect. Shadcn UI is built on Radix Primitives + Tailwind CSS, and it became the most-copied component library of 2025. Every team using Shadcn is installing Radix under the hood:
```bash
# This installs @radix-ui/react-dialog automatically
npx shadcn-ui add dialog
```
There's no Shadcn-equivalent built on React Aria (though Argos CI migrated from Radix to React Aria and published a migration guide).
## [When to Choose Each](https://www.pkgpulse.com/blog/react-aria-vs-radix-primitives-2026\#when-to-choose-each)
**Choose Radix Primitives if:**
- You're using Shadcn UI (Radix is already included)
- You want the largest community and most resources
- "Accessible enough" is acceptable (not strict WCAG AAA)
- Time-to-production is important
- Your design system is already partially built
**Choose React Aria if:**
- WCAG AA/AAA compliance is contractually required
- You need Date Picker, Color Picker, Drag & Drop, or Table with full keyboard navigation
- Your product is used by enterprise customers with accessibility requirements
- You build products used by people with disabilities (government, healthcare, education)
- Mobile screen reader support is critical
## [A Pragmatic Hybrid](https://www.pkgpulse.com/blog/react-aria-vs-radix-primitives-2026\#a-pragmatic-hybrid)
Some teams use Radix for most components and React Aria specifically for the complex ones Radix doesn't handle:
```bash
npm install @radix-ui/react-dialog @radix-ui/react-dropdown-menu # Most UI
npm install react-aria-components # Date pickers, complex patterns
```
This is a valid approach — they're both headless libraries that don't conflict.
## [Compare on PkgPulse](https://www.pkgpulse.com/blog/react-aria-vs-radix-primitives-2026\#compare-on-pkgpulse)
Track download trends for [Radix UI vs React Aria on PkgPulse](https://pkgpulse.com/).
See the live comparison
[View react aria vs. radix on PkgPulse →](https://www.pkgpulse.com/compare/react-aria-vs-radix)
## Comments
giscus
#### 0 reactions
#### 0 comments
WritePreview
[Styling with Markdown is supported](https://guides.github.com/features/mastering-markdown/ "Styling with Markdown is supported")
[Sign in with GitHub](https://giscus.app/api/oauth/authorize?redirect_uri=https%3A%2F%2Fwww.pkgpulse.com%2Fblog%2Freact-aria-vs-radix-primitives-2026)
### The 2026 JavaScript Stack Cheatsheet
One PDF: the best package for every category (ORMs, bundlers, auth, testing, state management). Used by 500+ devs. Free, updated monthly.
Get the Free Cheatsheet