Radix UI logo

Radix UI

React 生态中最流行的无样式、可组合、可访问的原生组件库

精选工具ReactHeadless UI可访问性Compound Component

Radix UI 是面向 React 的无样式组件库,专注于提供可访问、可组合的底层 UI 原语,是 shadcn/ui 的底层依赖,推动了 React 生态 headless 组件模式的普及。

Radix UI

React 生态中最流行的无样式、可组合、可访问的原生组件库

技术简介说明

Radix UI 是一个面向 React 的无样式(headless)组件库,专注于提供可访问性强、可组合、可定制的底层 UI 原语(Primitives)。它的核心理念是将行为逻辑与视觉样式解耦,开发者可以完全控制组件的外观,而不必重复实现复杂的交互逻辑和可访问性支持。

Radix UI 是 shadcn/ui 的底层依赖,shadcn/ui 的流行直接推动了 Radix UI 成为 React 生态中使用最广泛的 headless 组件库之一。

核心价值

  • 可访问性优先:WAI-ARIA 模式内置,键盘导航、焦点管理、屏幕阅读器支持开箱即用
  • 无样式设计:不强制任何视觉风格,完全由开发者控制 CSS
  • 可组合性:组件采用 compound component 模式,灵活组合
  • 开发者体验:API 简洁直观,TypeScript 类型完善

基本信息

项目详情
官网https://www.radix-ui.com
GitHubhttps://github.com/radix-ui/primitives
LicenseMIT
最新版本Radix Primitives v1.x(持续更新,2026年6月6日发布新版本)
主要维护者原 Modulz 团队创建,2022年被 WorkOS 收购后由 WorkOS 维护
技术栈React 18+、TypeScript
Stars15k+(GitHub)
npm 下载周下载量 5M+(含所有 @radix-ui/* 包)

产品矩阵

Radix UI 生态包含三个主要产品:

  1. Radix Primitives:无样式底层组件库(核心产品)
  2. Radix Themes:带样式的组件系统(基于 Primitives)
  3. Radix Colors:无障碍色彩系统

快速上手

安装

Radix UI 采用模块化设计,每个组件独立安装:

# 安装单个组件(推荐)
npm install @radix-ui/react-dialog
npm install @radix-ui/react-dropdown-menu
npm install @radix-ui/react-popover
 
# 或使用统一包(2026年2月推出)
npm install radix-ui

基础配置

Radix UI 无需特殊配置,直接导入使用:

import * as Dialog from '@radix-ui/react-dialog'
 
export function MyDialog() {
  return (
    <Dialog.Root>
      <Dialog.Trigger asChild>
        <button>打开对话框</button>
      </Dialog.Trigger>
      
      <Dialog.Portal>
        <Dialog.Overlay className="dialog-overlay" />
        <Dialog.Content className="dialog-content">
          <Dialog.Title>标题</Dialog.Title>
          <Dialog.Description>描述内容</Dialog.Description>
          
          <Dialog.Close asChild>
            <button>关闭</button>
          </Dialog.Close>
        </Dialog.Content>
      </Dialog.Portal>
    </Dialog.Root>
  )
}

最小示例:Tooltip

import * as Tooltip from '@radix-ui/react-tooltip'
import { useState } from 'react'
 
export function TooltipDemo() {
  const [open, setOpen] = useState(false)
  
  return (
    <Tooltip.Provider>
      <Tooltip.Root open={open} onOpenChange={setOpen}>
        <Tooltip.Trigger asChild>
          <button>悬停查看</button>
        </Tooltip.Trigger>
        
        <Tooltip.Portal>
          <Tooltip.Content side="top" className="tooltip-content">
            提示文本
            <Tooltip.Arrow className="tooltip-arrow" />
          </Tooltip.Content>
        </Tooltip.Portal>
      </Tooltip.Root>
    </Tooltip.Provider>
  )
}

核心概念与架构

Compound Component 模式

Radix UI 采用复合组件模式,通过命名空间组织相关子组件:

<Select.Root>
  <Select.Trigger />
  <Select.Value />
  <Select.Portal>
    <Select.Content>
      <Select.Viewport>
        <Select.Item value="option1">选项 1</Select.Item>
        <Select.Item value="option2">选项 2</Select.Item>
      </Select.Viewport>
    </Select.Content>
  </Select.Portal>
</Select.Root>

Portal 机制

弹出层类组件使用 Portal 渲染到 DOM 根部,避免 z-index 和溢出问题:

<Dialog.Portal>
  <Dialog.Content>...</Dialog.Content>
</Dialog.Portal>

Controlled 与 Uncontrolled 模式

所有交互组件同时支持受控和非受控状态:

// 非受控
<Dialog.Root defaultOpen={false}>
 
// 受控
<Dialog.Root open={isOpen} onOpenChange={setIsOpen}>

Context 状态共享

复合组件通过 React Context 共享内部状态,子组件自动同步:

<Accordion.Root type="single">
  <Accordion.Item value="item1">
    <Accordion.Trigger>标题</Accordion.Trigger>
    <Accordion.Content>内容</Accordion.Content>
  </Accordion.Item>
</Accordion.Root>

核心特性

1. 无障碍访问(Accessibility)

  • WAI-ARIA 模式内置,符合 WCAG 2.1 标准
  • 完整的键盘导航支持(Tab、Enter、Space、Arrow 键)
  • 焦点管理自动处理,包括焦点陷阱和焦点恢复
  • 屏幕阅读器友好,正确的 role、aria-* 属性
  • 支持减少动画偏好(prefers-reduced-motion)

2. 无样式设计(Unstyled)

  • 零样式注入,完全自定义外观
  • 支持任意 CSS 方案(CSS Modules、Tailwind、styled-components、Emotion)
  • 提供 asChild 属性,将组件行为附加到自定义元素
  • 数据属性(data-state)暴露组件状态,方便样式化
<button
  data-state={open ? 'open' : 'closed'}
  className="custom-button"
>
  {children}
</button>

3. 可组合性(Composability)

  • Compound Component 模式,灵活组合子组件
  • 支持组件嵌套和多层组合
  • asChild 属性实现组件合并,避免额外 DOM 节点
<Dialog.Trigger asChild>
  <CustomButton>打开</CustomButton>
</Dialog.Trigger>

4. TypeScript 支持

  • 完整的 TypeScript 类型定义
  • 泛型组件支持类型推断
  • IDE 智能提示完善
<Select.Root value={value} onValueChange={(val: string) => setValue(val)}>

5. 服务端渲染(SSR)

  • 完全支持 Next.js、Remix 等 SSR 框架
  • Portal 组件正确处理客户端 hydration
  • 零 hydration mismatch

6. 动画支持

  • 内置 CSS 动画支持,通过 data-state 驱动
  • 支持 Framer Motion、React Spring 等动画库
  • 提供动画生命周期钩子
.dialog-content[data-state='open'] {
  animation: fadeIn 200ms ease-out;
}
.dialog-content[data-state='closed'] {
  animation: fadeOut 200ms ease-in;
}

7. 主题集成

  • 不强制主题方案,兼容任意主题系统
  • Radix Colors 提供无障碍色彩系统(可选)
  • CSS 变量友好,支持动态主题切换

8. 模块化架构

  • 每个组件独立包,按需安装
  • Tree-shaking 友好,只打包使用的组件
  • 统一包 radix-ui 可选(2026年2月推出)

9. 受控/非受控双模式

  • 所有交互组件同时支持受控和非受控状态
  • API 一致性高,降低心智负担
  • 灵活的 defaultXxxonXxxChange 模式

10. 社区生态

  • shadcn/ui 底层依赖,生态成熟
  • 大量第三方组件和模板
  • 活跃的 Discord 社区

生态图

流程图画布 · 115%
Mermaid 流程图加载中...

上层生态

  • shadcn/ui:基于 Radix Primitives + Tailwind CSS 的组件集合(最流行)
  • 自定义设计系统:企业基于 Radix 构建内部组件库
  • 模板和主题:社区提供的预配置模板

配套工具

  • Radix Themes:带样式的组件系统,快速原型开发
  • Radix Colors:无障碍色彩系统,12 级色阶
  • @radix-ui/react-icons:图标库(已废弃,推荐使用 Lucide)

适用场景

1. 自定义设计系统

场景:需要构建完全定制的品牌设计系统,不允许第三方样式干扰

优势

  • 零样式注入,完全控制视觉表现
  • 内置可访问性,避免重复实现复杂交互
  • Compound Component 模式提供最大灵活性

案例:中大型企业的内部组件库,需要严格遵循品牌规范

2. shadcn/ui 项目

场景:使用 shadcn/ui 作为组件基础,需要理解底层原理

优势

  • shadcn/ui 底层依赖 Radix Primitives
  • 理解 Radix 有助于定制 shadcn 组件
  • 遇到 bug 可直接定位到 Radix 层

案例:基于 shadcn/ui 的 SaaS 产品、管理后台

3. 高可访问性要求项目

场景:政府网站、教育平台、医疗系统,需符合 WCAG 2.1 AA/AAA 标准

优势

  • WAI-ARIA 模式内置,无需手动实现
  • 键盘导航、焦点管理开箱即用
  • 屏幕阅读器测试充分

案例:政府门户网站、在线学习平台、医疗信息系统

4. 复杂交互组件

场景:需要实现对话框、下拉菜单、选项卡、手风琴等复杂交互

优势

  • 处理了复杂的焦点陷阱、键盘导航、状态管理
  • 边界情况(如嵌套对话框、动态内容)已充分测试
  • 避免重复造轮子

案例:企业级应用后台、SaaS 平台、内容管理系统

5. 多主题应用

场景:需要支持亮色/暗色主题切换,或动态主题

优势

  • 无样式设计,主题切换不冲突
  • data-state 属性方便主题样式绑定
  • CSS 变量友好

案例:多租户 SaaS 平台、用户可自定义主题的应用

6. 服务端渲染项目

场景:Next.js、Remix 等 SSR 框架项目,需要 SEO 优化

优势

  • 完全支持 SSR,零 hydration mismatch
  • Portal 正确处理客户端渲染
  • 性能优化良好

案例:电商网站、内容平台、营销落地页

7. 微前端架构

场景:多个独立部署的前端应用需要共享组件逻辑

优势

  • 模块化设计,按需加载
  • 无样式冲突,不同应用可使用不同样式
  • 独立版本管理

案例:大型微前端系统、多团队协作项目

开发与工程化

项目结构

node_modules/
├── @radix-ui/
│   ├── react-dialog/
│   ├── react-dropdown-menu/
│   ├── react-popover/
│   └── ...

TypeScript 配置

{
  "compilerOptions": {
    "strict": true,
    "jsx": "react-jsx",
    "moduleResolution": "bundler"
  }
}

ESLint 配置

module.exports = {
  extends: [
    'plugin:react/recommended',
    'plugin:react-hooks/recommended',
  ],
  rules: {
    'react/prop-types': 'off', // TypeScript 已处理
  },
}

测试策略

import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import * as Dialog from '@radix-ui/react-dialog'
 
test('打开对话框', async () => {
  render(
    <Dialog.Root>
      <Dialog.Trigger>打开</Dialog.Trigger>
      <Dialog.Portal>
        <Dialog.Content>
          <Dialog.Title>标题</Dialog.Title>
        </Dialog.Content>
      </Dialog.Portal>
    </Dialog.Root>
  )
  
  await userEvent.click(screen.getByText('打开'))
  expect(screen.getByRole('dialog')).toBeInTheDocument()
})

构建优化

  • Tree Shaking:每个组件独立包,只打包使用的组件
  • 代码分割:动态导入大组件(如 DataTable)
  • Bundle 分析:使用 @next/bundle-analyzer 检查体积
// 动态导入
const DatePicker = dynamic(() => import('./DatePicker'), {
  ssr: false,
})

调试技巧

// 检查 data-state
<button data-state={open ? 'open' : 'closed'}>
 
// 调试焦点
<div tabIndex={-1} onFocus={() => console.log('focused')}>
 
// 检查 ARIA 属性
<Dialog.Content aria-describedby="description">

性能与安全

性能优化

  1. 按需加载:每个组件独立包,避免全量导入
  2. Portal 优化:弹出层渲染到 DOM 根部,减少重排
  3. 动画性能:使用 CSS transform/opacity,避免 layout 抖动
  4. Memoization:合理使用 React.memouseMemo
  5. 虚拟滚动:大数据列表结合 @radix-ui/react-scroll-area + 虚拟滚动
// Portal 自动优化 z-index
<Dialog.Portal container={document.getElementById('root')}>
  <Dialog.Content>...</Dialog.Content>
</Dialog.Portal>

Bundle 体积

组件gzip 体积
Dialog~3.5 KB
Dropdown Menu~4.2 KB
Select~5.1 KB
Tabs~2.8 KB
Tooltip~2.3 KB

安全考虑

  1. XSS 防护:不直接注入 HTML,使用 dangerouslySetInnerHTML 需谨慎
  2. 焦点陷阱:对话框类组件自动处理焦点陷阱,防止焦点逃逸
  3. 点击外部关闭:安全处理点击事件,避免误触发
  4. 键盘事件:防止键盘事件冒泡导致的意外行为
// 安全的 HTML 注入
<div dangerouslySetInnerHTML={{ __html: sanitizedHTML }} />

可访问性测试

import { axe, toHaveNoViolations } from 'jest-axe'
 
test('无可访问性违规', async () => {
  const { container } = render(<Dialog.Root>...</Dialog.Root>)
  const results = await axe(container)
  expect(results).toHaveNoViolations()
})

技术对比

Radix UI vs Headless UI vs React Aria

特性Radix UIHeadless UIReact Aria
维护状态WorkOS 维护,更新放缓Tailwind Labs 维护Adobe 维护
组件数量30+10+40+
无障碍WAI-ARIA 内置WAI-ARIA 内置WAI-ARIA 内置
样式方案无样式无样式无样式
TypeScript完善完善完善
SSR 支持完整完整完整
生态shadcn/ui 依赖Tailwind 生态Adobe 生态
学习曲线中等
适用场景自定义设计系统Tailwind 项目企业级应用

Radix UI vs Base UI

特性Radix UIBase UI
维护团队WorkOS(原团队成员已离开)MUI 团队(原 Radix 核心成员)
成熟度成熟稳定v1.0(2025年12月发布)
API 设计Compound Component类似 Radix,有改进
性能优秀更优(架构优化)
生态shadcn/ui 依赖shadcn/ui v4 支持
社区成熟社区新兴社区
推荐度现有项目继续使用新项目可考虑

何时选择 Radix UI

选择 Radix UI

  • 需要完全控制视觉样式
  • 项目已有 shadcn/ui 依赖
  • 团队熟悉 Compound Component 模式
  • 需要成熟的社区支持

不选择 Radix UI

  • 需要开箱即用的样式(选择 MUI、Ant Design)
  • 偏好 Tailwind 原子化 CSS(选择 Headless UI)
  • 新项目且追求最新技术(考虑 Base UI)

最佳实践

1. 组件封装

// 封装通用对话框
function BaseDialog({
  open,
  onOpenChange,
  title,
  children,
}: {
  open: boolean
  onOpenChange: (open: boolean) => void
  title: string
  children: React.ReactNode
}) {
  return (
    <Dialog.Root open={open} onOpenChange={onOpenChange}>
      <Dialog.Portal>
        <Dialog.Overlay className="dialog-overlay" />
        <Dialog.Content className="dialog-content">
          <Dialog.Title>{title}</Dialog.Title>
          {children}
          <Dialog.Close className="dialog-close">×</Dialog.Close>
        </Dialog.Content>
      </Dialog.Portal>
    </Dialog.Root>
  )
}

2. 样式方案

// Tailwind CSS + data-state
<button
  data-state={open ? 'open' : 'closed'}
  className="
    bg-gray-100 
    data-[state=open]:bg-gray-200 
    data-[state=closed]:bg-gray-100
  "
>

3. 动画实现

// CSS 动画
.dialog-content {
  animation: fadeIn 200ms ease-out;
}
.dialog-content[data-state='closed'] {
  animation: fadeOut 200ms ease-in;
}
 
// Framer Motion
<Dialog.Content
  asChild
  initial={{ opacity: 0, scale: 0.95 }}
  animate={{ opacity: 1, scale: 1 }}
  exit={{ opacity: 0, scale: 0.95 }}
>

4. 表单集成

import { useForm } from 'react-hook-form'
 
function FormWithSelect() {
  const { register, handleSubmit } = useForm()
  
  return (
    <form onSubmit={handleSubmit(data => console.log(data))}>
      <Select.Root name="category" {...register('category')}>
        <Select.Trigger />
        <Select.Content>
          <Select.Item value="tech">技术</Select.Item>
          <Select.Item value="design">设计</Select.Item>
        </Select.Content>
      </Select.Root>
    </form>
  )
}

5. 错误处理

// 异步加载数据
function AsyncSelect() {
  const [data, setData] = useState([])
  const [error, setError] = useState(null)
  
  useEffect(() => {
    fetchData()
      .then(setData)
      .catch(setError)
  }, [])
  
  if (error) return <div>加载失败</div>
  
  return (
    <Select.Root>
      <Select.Trigger />
      <Select.Content>
        {data.map(item => (
          <Select.Item key={item.id} value={item.id}>
            {item.name}
          </Select.Item>
        ))}
      </Select.Content>
    </Select.Root>
  )
}

6. 性能优化

// 避免不必要的重渲染
const MemoizedDialog = React.memo(({ open, onClose }) => (
  <Dialog.Root open={open} onOpenChange={onClose}>
    <Dialog.Content>...</Dialog.Content>
  </Dialog.Root>
))
 
// 延迟加载大组件
const DataTable = React.lazy(() => import('./DataTable'))

技术局限与边界

已知问题

  1. 维护放缓:2022年被 WorkOS 收购后,原核心团队成员已离开,维护力度下降
  2. Issue 积压:GitHub Issues 响应慢,PR 合并周期长
  3. 无新组件计划:缺少 DataTable、DatePicker 等复杂组件
  4. 文档更新滞后:部分 API 文档未及时更新

设计边界

  1. 无样式限制:需要自行实现所有样式,学习成本较高
  2. Compound Component 复杂性:深层嵌套时调试困难
  3. Portal 限制:某些 SSR 场景需特殊处理
  4. 动画集成:复杂动画需额外集成第三方库

何时不该使用

  • 快速原型:需要开箱即用的样式(选择 MUI、Ant Design)
  • 简单项目:无需复杂交互,直接使用 HTML + CSS
  • 非 React 项目:Radix 仅支持 React
  • Vue/Svelte 项目:选择对应的 headless 库(Headless UI Vue、Svelte Headless)

迁移风险

  • Base UI 竞争:shadcn/ui v4 已支持 Base UI,新项目可能倾向 Base UI
  • 长期维护不确定性:WorkOS 未明确 Radix 的长期路线图

学习资源

官方资源

视频教程

社区资源

实战教程

中文资源

2026 年现状

维护状态

  • WorkOS 维护:仍由 WorkOS 维护,但团队规模缩小,原核心成员已离开
  • 更新频率:2026年6月6日发布新版本,包含组合 API 改进和受控上下文菜单
  • 统一包:2026年2月推出 radix-ui 统一包,减少 node_modules 体积

生态变化

  • Base UI 崛起:原 Radix 核心成员创建的 Base UI 于 2025年12月发布 v1.0,性能更优
  • shadcn/ui 双轨支持:shadcn/ui v4 同时支持 Radix UI 和 Base UI,用户可选择
  • 社区迁移讨论:部分新项目倾向选择 Base UI,但 Radix 仍是最成熟的选项

推荐策略

现有项目

  • 继续使用 Radix UI,成熟稳定,生态完善
  • 关注 Base UI 发展,但不急于迁移

新项目

  • 需要快速迭代:选择 Radix UI(成熟文档和社区)
  • 追求性能和新技术:考虑 Base UI(原团队打造)
  • 使用 shadcn/ui:默认使用 Radix UI,v4 后可选择 Base UI

长期展望

Radix UI 仍是 React 生态中最成熟的 headless 组件库,尽管维护力度有所下降,但:

  • API 稳定,破坏性变更少
  • shadcn/ui 的流行保证了生态活力
  • 企业级应用可放心使用

但需关注:

  • WorkOS 的长期维护承诺
  • Base UI 的发展速度
  • 社区迁移趋势

文档版本:v1.0
最后更新:2026-07-07
适用版本:Radix Primitives 2026年6月版本
参考资料