Headless UI logo

Headless UI

Tailwind Labs 出品的无样式、完全可访问的 UI 组件库

精选工具ReactHeadless UITailwind CSSVue

Headless UI 是 Tailwind Labs 开发的无样式可访问组件库,将交互逻辑、状态管理和无障碍能力与视觉样式彻底解耦,与 Tailwind CSS 深度集成,同时支持 React 和 Vue。

Headless UI

Tailwind Labs 出品的无样式、完全可访问的 UI 组件库,为 React 和 Vue 提供开箱即用的交互逻辑与无障碍支持,与 Tailwind CSS 深度集成。

技术简介说明

Headless UI 是由 Tailwind CSS 背后的团队(Tailwind Labs)开发和维护的一套无样式(unstyled)、完全可访问(accessible)的 UI 组件库。它的核心理念是将组件的交互逻辑、状态管理和无障碍能力视觉样式彻底解耦——开发者获得经过严格测试的组件行为(如键盘导航、焦点管理、ARIA 属性),同时完全掌控最终的视觉呈现。

Headless UI 与 Tailwind CSS 天然契合,组件 API 设计充分考虑了 utility-first 的使用场景,但也可以与任何 CSS 方案(CSS Modules、Styled Components、UnoCSS 等)搭配使用。

核心价值主张

  • 零样式绑定:组件不注入任何 CSS,不产生样式冲突
  • 无障碍优先:完整的 WAI-ARIA 实现、键盘导航、焦点管理
  • Tailwind 原生体验:API 设计与 Tailwind 的 utility class 思维一致
  • 最小 API 表面积:每个组件暴露最少的 props,降低学习成本
  • 类型安全:完整的 TypeScript 类型定义

基本信息

项目信息
名称Headless UI
官网https://headlessui.com
GitHubtailwindlabs/headlessui
当前版本v2.2.x(2026 年 7 月)
LicenseMIT
框架支持React(18+)、Vue(3.x)
包管理器npm / pnpm / yarn / bun
TypeScript原生支持,内置类型定义
主要维护者Tailwind Labs Inc.
核心开发者Robin Malfait(主要维护者)、Adam Wathan(Tailwind CSS 创始人)
npm 包名@headlessui/react@headlessui/vue
GitHub Stars~26,000+(2026 年)
npm 周下载量~2,000,000+(2026 年)
Monorepo 管理pnpm workspace

快速上手

安装

# npm
npm install @headlessui/react
 
# pnpm(推荐)
pnpm add @headlessui/react
 
# yarn
yarn add @headlessui/react
 
# Vue 版本
pnpm add @headlessui/vue

基础配置

Headless UI 无需额外配置,安装后即可使用。建议搭配 Tailwind CSS:

# 安装 Tailwind CSS(如尚未安装)
pnpm add -D tailwindcss @tailwindcss/postcss
 
# 在 postcss.config.js 或 postcss.config.mjs 中配置
// postcss.config.mjs
export default {
  plugins: {
    '@tailwindcss/postcss': {},
  },
}

最小示例

React 示例 — Dropdown Menu

import { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/react'
 
export default function Example() {
  return (
    <Menu>
      <MenuButton className="rounded-md bg-indigo-600 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-500">
        选项菜单
      </MenuButton>
 
      <MenuItems
        anchor="bottom"
        className="mt-1 w-48 origin-top-right rounded-xl border border-gray-200 bg-white p-1 shadow-lg"
      >
        <MenuItem>
          {({ focus }) => (
            <button className={`block w-full rounded-lg px-3 py-1.5 text-sm text-gray-800 ${focus ? 'bg-indigo-100' : ''}`}>
              编辑资料
            </button>
          )}
        </MenuItem>
        <MenuItem>
          {({ focus }) => (
            <button className={`block w-full rounded-lg px-3 py-1.5 text-sm text-gray-800 ${focus ? 'bg-indigo-100' : ''}`}>
              设置
            </button>
          )}
        </MenuItem>
        <MenuItem>
          {({ focus }) => (
            <button className={`block w-full rounded-lg px-3 py-1.5 text-sm text-red-600 ${focus ? 'bg-red-50' : ''}`}>
              退出登录
            </button>
          )}
        </MenuItem>
      </MenuItems>
    </Menu>
  )
}

Vue 示例 — Dropdown Menu

<template>
  <Menu>
    <MenuButton
      class="rounded-md bg-indigo-600 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-500"
    >
      选项菜单
    </MenuButton>
 
    <MenuItems
      anchor="bottom"
      class="mt-1 w-48 origin-top-right rounded-xl border border-gray-200 bg-white p-1 shadow-lg"
    >
      <MenuItem v-slot="{ focus }">
        <button
          :class="[
            'block w-full rounded-lg px-3 py-1.5 text-sm text-gray-800',
            focus ? 'bg-indigo-100' : ''
          ]"
        >
          编辑资料
        </button>
      </MenuItem>
    </MenuItems>
  </Menu>
</template>
 
<script setup>
import { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/vue'
</script>

核心概念与架构

架构设计

Headless UI 采用**组件组合(Composition)**模式,将复杂 UI 拆解为多个子组件,由顶层组件统一协调状态:

┌─────────────────────────────────────────┐
│           顶层组件(状态容器)              │
│  ┌───────────┐  ┌──────────────────┐    │
│  │ 触发器组件 │  │  内容/面板组件     │    │
│  │ (Button)  │  │  (Panel/Items)   │    │
│  └───────────┘  └──────────────────┘    │
│         ↕  内部状态同步(Context)         │
└─────────────────────────────────────────┘

核心设计原则

  1. 组合式 API:通过 &lt;Menu&gt;&lt;MenuButton&gt; + &lt;MenuItems&gt;&lt;MenuItem&gt; 的组合方式构建 UI
  2. Render Props / Scoped Slots:子组件通过 render function(React)或作用域插槽(Vue)暴露内部状态
  3. 受控与非受控模式:所有交互组件支持受控(value + onChange)和非受控两种使用方式
  4. 内置定位系统:v2.0 起内置 anchor positioning,无需引入 Floating UI 等第三方定位库
  5. Transitions 集成:内置 &lt;Transition&gt; 组件,支持进入/离开动画与 CSS class 自动管理

状态管理模式

用户交互(点击/键盘)
    ↓
组件内部 reducer 计算新状态
    ↓
Context 分发状态给子组件
    ↓
子组件根据状态渲染(focus / active / open / checked)
    ↓
外部回调触发(onChange / onClose)

无障碍(Accessibility)架构

Headless UI 的无障碍不是附加层,而是架构级内置

  • ARIA 属性:自动为每个组件添加正确的 rolearia-expandedaria-activedescendantaria-labelledby
  • 键盘导航:完整支持 Tab、Enter、Space、Escape、Arrow Keys、Home、End、PageUp、PageDown
  • 焦点管理:打开时自动聚焦、关闭时恢复焦点、焦点陷阱(Focus Trap)
  • 屏幕阅读器:正确的 announce 策略和 live region 处理
  • RTL 支持:双向布局下的方向键导航自动适配

核心特性

1. 完全无样式(Completely Unstyled)

组件不产生任何 CSS 输出。所有视觉样式完全由开发者控制,避免了:

  • 样式覆盖和优先级冲突
  • CSS 特异性(specificity)问题
  • 主题切换时的样式 hack

2. 完整的 WAI-ARIA 无障碍

每个组件严格按照 WAI-ARIA Authoring Practices 实现:

  • Dialog(Modal):焦点陷阱、Escape 关闭、背景点击关闭
  • Listbox:aria-activedescendant 模式、多选支持
  • Combobox:完整的自动补全 ARIA 模式
  • Menu:role="menu" + aria-activedescendant
  • Tabs:role="tablist" + aria-selected + 方向键切换

3. 内置锚点定位(Anchor Positioning)

v2.0 起内置浮动元素定位,替代第三方方案:

<ListboxOptions anchor="bottom start" offset={8}>
  {/* 自动定位在触发器下方,处理边界碰撞 */}
</ListboxOptions>

支持属性:

  • anchor:定位方向(bottomtopleftright 及其组合)
  • offset:偏移距离
  • gap:与锚点元素的间距
  • padding:视口边缘内边距
  • 自动翻转(flip)和尺寸调整(resize)

4. 键盘交互支持

所有交互组件提供完整的键盘操作:

组件支持的键盘操作
Menu↑↓ 导航、Enter/Space 激活、Escape 关闭、Home/End 首尾跳转
Listbox↑↓ 导航、Enter/Space 选择、Escape 关闭、Home/End、类型搜索
Combobox↑↓ 导航、Enter 选择、Escape 关闭、字符输入过滤
DialogEscape 关闭、Tab 焦点陷阱
Tabs←→ 切换标签页、Home/End 首尾跳转
DisclosureEnter/Space 展开/折叠

5. Transition 动画系统

内置声明式动画组件:

<Transition
  show={isOpen}
  enter="transition ease-out duration-200"
  enterFrom="opacity-0 scale-95"
  enterTo="opacity-100 scale-100"
  leave="transition ease-in duration-150"
  leaveFrom="opacity-100 scale-100"
  leaveTo="opacity-0 scale-95"
>
  {/* 需要动画的元素 */}
</Transition>

支持:

  • CSS class 驱动的进入/离开动画
  • beforeEnter / afterEnter / beforeLeave / afterLeave 回调
  • 嵌套 Transition 协调
  • @headlessui/react 的组件状态自动联动

6. 受控与非受控双模式

// 非受控(简单场景)
<Listbox>
  <ListboxButton>Select</ListboxButton>
  <ListboxOptions>...</ListboxOptions>
</Listbox>
 
// 受控(复杂状态管理)
<Listbox value={selected} onChange={setSelected}>
  <ListboxButton>{selected.name}</ListboxButton>
  <ListboxOptions>...</ListboxOptions>
</Listbox>

7. 多框架支持(React + Vue)

同一套 API 设计,同时提供 React 和 Vue 3 实现:

  • React:基于 Hooks 和 Context,支持 Server Components
  • Vue:基于 Composition API 和 Provide/Inject,支持 Scoped Slots

8. 表单集成

v2.0+ 提供表单相关组件:

  • &lt;Field&gt;:表单字段容器,自动关联 label、描述、错误信息
  • &lt;Label&gt;:自动关联到最近的可访问表单控件
  • &lt;Description&gt;:为表单控件提供辅助描述
  • &lt;FieldError&gt;:错误信息展示
  • &lt;Input&gt;&lt;Textarea&gt;&lt;Select&gt;:无样式表单控件
  • &lt;Checkbox&gt;&lt;Switch&gt;&lt;RadioGroup&gt;:交互控件
  • 支持隐藏字段提交(&lt;Hidden&gt;),与原生 form 行为兼容

9. 类型安全(TypeScript First)

  • 所有组件均提供完整的 TypeScript 类型定义
  • 泛型支持(如 Listbox&lt;T&gt; 可推断选项类型)
  • Render props 参数类型自动推导
  • as prop 的 ElementType 约束

10. 多态渲染(Polymorphic Components)

通过 as prop 改变组件渲染的 HTML 元素:

<MenuButton as="a" href="/settings">
  设置链接
</MenuButton>
 
<ListboxButton as={Fragment}>
  <button className="custom-btn">自定义按钮</button>
</ListboxButton>

生态图

与 Tailwind CSS 生态的关系

                    Tailwind Labs 生态
┌──────────────────────────────────────────────┐
│                                              │
│  ┌──────────────┐  ┌─────────────────────┐  │
│  │ Tailwind CSS │  │   Headless UI       │  │
│  │ (样式引擎)   │←→│ (交互逻辑 + A11y)   │  │
│  └──────────────┘  └─────────────────────┘  │
│         ↓                    ↓               │
│  ┌──────────────────────────────────────┐   │
│  │         Tailwind UI(付费)            │   │
│  │    预制的完整组件(Tailwind + HUI)     │   │
│  └──────────────────────────────────────┘   │
└──────────────────────────────────────────────┘

相关生态项目

项目说明
Tailwind CSS最佳搭配样式方案
Tailwind UITailwind Labs 付费组件模板,基于 Headless UI 构建
nuxt-headlessuiNuxt 3 官方模块集成
Floating UIv2.0 前用于定位的第三方库,现已内置替代
daisyUI可搭配使用的 Tailwind 组件主题库
HeroiconsTailwind Labs 出品的 SVG 图标库

社区生态

项目说明
headlessui-floatFloating UI 集成(v2 前常用)
headlessui-transition扩展动画能力
VueUse提供与 Headless UI 配合使用的 Vue 组合式工具函数

在 Headless UI 生态之上的常见方案

┌──────────────────────────────────────────────┐
│           应用层(你的项目)                    │
├──────────────────────────────────────────────┤
│  方案 A:Headless UI + Tailwind CSS 手搓      │
│  方案 B:Headless UI + Tailwind UI 模板        │
│  方案 C:Headless UI + 自定义设计系统           │
├──────────────────────────────────────────────┤
│  Headless UI(交互逻辑 + 无障碍)               │
├──────────────────────────────────────────────┤
│  Tailwind CSS / 其他 CSS 方案(视觉样式)       │
└──────────────────────────────────────────────┘

适用场景

1. 需要完全自定义视觉设计的项目

当你有一套独特的设计系统,不想被预制样式约束时,Headless UI 提供交互逻辑,你负责视觉呈现。

2. 无障碍要求严格的企业级应用

金融、政务、医疗等对 WCAG 合规性有严格要求的场景,Headless UI 的 ARIA 实现经过充分测试。

3. Tailwind CSS 技术栈项目

Headless UI 的 API 设计与 Tailwind utility-first 思维高度一致,是 Tailwind 生态的原生选择。

4. 组件数量需求适中的项目

相比 Radix UI(30+ 组件)和 React Aria(40+ 组件),Headless UI 提供 ~15 个精选核心组件,适合不需要大量复杂组件的项目。

5. Vue + React 双栈团队

Headless UI 同时提供 React 和 Vue 实现,API 设计一致。对于同时维护 React 和 Vue 项目的团队,减少认知负担。

6. 轻量级 Bundle 需求

Headless UI 按需引入组件,Tree-shaking 友好,最终打包体积小。适合对包体积敏感的应用。

7. 渐进式采用

可以只引入需要的个别组件(如只用 Dialog 和 Switch),不影响项目其他部分,渐进式采用。

开发与工程化

项目结构(源码)

Headless UI 采用 monorepo 结构:

headlessui/
├── packages/
│   ├── react/           # React 实现
│   │   ├── src/
│   │   │   ├── components/
│   │   │   │   ├── dialog/
│   │   │   │   ├── disclosure/
│   │   │   │   ├── listbox/
│   │   │   │   ├── menu/
│   │   │   │   ├── popover/
│   │   │   │   ├── tabs/
│   │   │   │   ├── combobox/
│   │   │   │   ├── transition/
│   │   │   │   └── ...
│   │   │   └── hooks/
│   │   └── package.json
│   └── vue/             # Vue 实现
│       ├── src/
│       └── package.json
├── playgrounds/         # 开发调试 playground
├── pnpm-workspace.yaml
└── package.json

开发命令

# 克隆仓库
git clone https://github.com/tailwindlabs/headlessui.git
cd headlessui
 
# 安装依赖
pnpm install
 
# 运行测试
pnpm test
 
# 构建
pnpm build
 
# 运行 playground(React)
pnpm --filter @headlessui/react-playground dev
 
# 运行 playground(Vue)
pnpm --filter @headlessui/vue-playground dev
 
# 类型检查
pnpm typecheck
 
# Lint
pnpm lint

测试策略

Headless UI 使用多层次的测试策略:

  • 单元测试:使用 Vitest,覆盖组件状态逻辑
  • 组件测试:使用 Testing Library(@testing-library/react / @testing-library/vue
  • 无障碍测试:验证 ARIA 属性、键盘导航、焦点管理
  • 交互测试:模拟用户操作(点击、键盘、焦点事件)
  • 视觉回归测试:通过 Playwright 进行截图对比

贡献指南

  1. Fork 仓库并创建 feature 分支
  2. 确保所有测试通过:pnpm test
  3. 遵循项目代码风格(ESLint + Prettier)
  4. 提交 PR 前通过 CI 检查
  5. Bug 修复需附带回归测试

版本发布

  • 遵循语义化版本(SemVer)
  • CHANGELOG 维护在 GitHub Releases
  • 使用 Changesets 管理版本变更

性能与安全

性能表现

维度表现
Bundle Size按需引入,单个组件 gzipped 后 2-5KB
Tree Shaking完整支持,未使用的组件不会打入最终包
运行时开销极低——仅状态管理和事件处理,无 DOM 操作
React 优化支持 React.memouseCallback 等优化手段
SSR/SSG完整支持 Next.js App Router、Nuxt 3 等 SSR 框架
渲染性能组件使用 Context 而非 prop drilling,避免不必要的重渲染

Bundle Size 参考(v2.x,gzipped)

@headlessui/react 单个组件:
  Dialog       ~3.5 KB
  Listbox      ~4.2 KB
  Combobox     ~5.1 KB
  Menu         ~3.0 KB
  Switch       ~1.8 KB
  Transition   ~2.5 KB
  Tabs         ~2.8 KB

完整引入(不推荐)~18 KB gzipped

安全措施

  • XSS 防护:组件不直接操作 innerHTML,全部通过 React/Vue 的虚拟 DOM 渲染
  • 无外部依赖:核心组件几乎无第三方运行时依赖,减少供应链风险
  • DOMPurify 无需求:由于无样式注入,不存在 CSS 注入攻击面
  • 依赖审计:定期通过 pnpm audit 检查依赖安全
  • 焦点安全:焦点陷阱实现经过安全审计,防止焦点逃逸导致的交互漏洞

技术对比

与同类 Headless UI 库对比

特性Headless UIRadix UIReact AriaArk UIBase UI
维护者Tailwind LabsWorkOSAdobeChakra 团队MUI
框架支持React + VueReact onlyReact onlyReact onlyReact only
组件数量~15~30~40~30~10(开发中)
无样式✅ 完全无样式✅ 完全无样式✅ 完全无样式✅ 完全无样式✅ 完全无样式
无障碍✅ WAI-ARIA 完整✅ WAI-ARIA 完整✅ WAI-ARIA 最完整✅ WAI-ARIA 完整✅ WAI-ARIA 完整
Tailwind 集成✅ 原生设计⚠️ 需手动适配⚠️ 需手动适配⚠️ 需手动适配⚠️ 需手动适配
内置定位✅ v2.0+❌ 需 @floating-ui❌ 需 @react-aria/overlays❌ 需 @floating-ui❌ 需 @floating-ui
Vue 支持✅ 原生❌ 无❌ 无❌ 无❌ 无
API 风格组合式组合式Hooks组合式组合式
学习曲线
TypeScript✅ 完整✅ 完整✅ 完整✅ 完整✅ 完整
SSR 支持
包体积小(~18KB 全量)

与 shadcn/ui 的关系

┌────────────────────────────────────────────┐
│              shadcn/ui                      │
│    (预制样式 + 复制粘贴模式)                 │
├────────────────────────────────────────────┤
│     Radix UI Primitives                    │
│  或  Base UI(2025 起可选)                  │
│    (底层无障碍交互逻辑)                      │
└────────────────────────────────────────────┘
  • shadcn/ui 不是传统 npm 库,而是复制粘贴的组件集合
  • shadcn/ui 底层使用 Radix UI(2025 年起也支持 Base UI)
  • Headless UI 与 shadcn/ui 是不同层级的方案:Headless UI 是底层 primitives,shadcn/ui 是上层完整组件
  • 选择 Headless UI 意味着自己组合 primitives 构建组件;选择 shadcn/ui 意味着直接获得预制组件

选型决策树

需要预制组件、快速出原型?
  ├─ 是 → shadcn/ui(基于 Radix)
  └─ 否 ↓
需要 Vue 支持?
  ├─ 是 → Headless UI ✅(唯一同时支持 React + Vue 的主流方案)
  └─ 否 ↓
使用 Tailwind CSS?
  ├─ 是 → Headless UI ✅(原生集成)
  └─ 否 ↓
需要最多的组件覆盖?
  ├─ 是 → Radix UI 或 React Aria
  └─ 否 ↓
追求最简洁的 API?
  ├─ 是 → Headless UI ✅
  └─ 否 → 按需选择

最佳实践

1. 组件封装与复用

将常用的 Headless UI 组合封装为项目级组件:

// components/CustomSelect.tsx
import {
  Listbox,
  ListboxButton,
  ListboxOption,
  ListboxOptions,
} from '@headlessui/react'
import { ChevronDownIcon } from '@heroicons/react/20/solid'
 
interface CustomSelectProps<T> {
  label: string
  value: T
  options: { value: T; label: string }[]
  onChange: (value: T) => void
}
 
export function CustomSelect<T>({
  label,
  value,
  options,
  onChange,
}: CustomSelectProps<T>) {
  return (
    <Listbox value={value} onChange={onChange}>
      <Label className="block text-sm font-medium text-gray-700">
        {label}
      </Label>
      <ListboxButton className="relative w-full cursor-default rounded-lg bg-white py-2 pl-3 pr-10 text-left shadow-md border border-gray-300">
        {options.find(o => o.value === value)?.label}
        <ChevronDownIcon className="absolute right-3 top-1/2 -translate-y-1/2 h-5 w-5 text-gray-400" />
      </ListboxButton>
      <ListboxOptions
        anchor="bottom"
        className="w-[var(--button-width)] rounded-xl bg-white p-1 shadow-lg border border-gray-200"
      >
        {options.map(option => (
          <ListboxOption
            key={String(option.value)}
            value={option.value}
            className={({ active, selected }) =>
              `relative cursor-default select-none py-2 pl-10 pr-4 rounded-lg ${
                active ? 'bg-indigo-100 text-indigo-900' : 'text-gray-900'
              } ${selected ? 'font-medium' : 'font-normal'}`
            }
          >
            {({ selected }) => (
              <>
                {selected && (
                  <span className="absolute left-3 top-1/2 -translate-y-1/2 text-indigo-600">

                  </span>
                )}
                {option.label}
              </>
            )}
          </ListboxOption>
        ))}
      </ListboxOptions>
    </Listbox>
  )
}

2. 与 React Hook Form 集成

import { Controller, useForm } from 'react-hook-form'
import { Listbox, ListboxButton, ListboxOption, ListboxOptions } from '@headlessui/react'
 
function FormExample() {
  const { control, handleSubmit } = useForm()
 
  return (
    <form onSubmit={handleSubmit(data => console.log(data))}>
      <Controller
        name="country"
        control={control}
        rules={{ required: '请选择国家' }}
        render={({ field: { onChange, value } }) => (
          <Listbox value={value} onChange={onChange}>
            <ListboxButton>选择国家</ListboxButton>
            <ListboxOptions anchor="bottom">
              <ListboxOption value="cn">中国</ListboxOption>
              <ListboxOption value="us">美国</ListboxOption>
              <ListboxOption value="jp">日本</ListboxOption>
            </ListboxOptions>
          </Listbox>
        )}
      />
    </form>
  )
}

3. Transition 动画最佳实践

// 使用 CSS 变量控制动画时长,便于主题统一
<Transition
  show={isOpen}
  enter="transition duration-200 ease-out"
  enterFrom="opacity-0 translate-y-1"
  enterTo="opacity-100 translate-y-0"
  leave="transition duration-150 ease-in"
  leaveFrom="opacity-100 translate-y-0"
  leaveTo="opacity-0 translate-y-1"
>
  <DialogPanel>
    {/* 对话框内容 */}
  </DialogPanel>
</Transition>

4. Combobox 异步搜索

import { useState } from 'react'
import { Combobox, ComboboxInput, ComboboxOption, ComboboxOptions } from '@headlessui/react'
 
function AsyncSearch() {
  const [query, setQuery] = useState('')
  const [results, setResults] = useState([])
  const [selected, setSelected] = useState(null)
 
  async function handleQueryChange(value) {
    setQuery(value)
    if (value.length > 2) {
      const res = await fetch(`/api/search?q=${value}`)
      const data = await res.json()
      setResults(data)
    }
  }
 
  return (
    <Combobox value={selected} onChange={setSelected} immediate>
      <ComboboxInput
        className="w-full rounded-lg border border-gray-300 px-3 py-2"
        onChange={e => handleQueryChange(e.target.value)}
        placeholder="搜索用户..."
        displayValue={item => item?.name ?? ''}
      />
      <ComboboxOptions anchor="bottom" className="w-[var(--input-width)] bg-white shadow-lg rounded-xl p-1">
        {results.map(user => (
          <ComboboxOption
            key={user.id}
            value={user}
            className={({ focus }) =>
              `rounded-lg px-3 py-2 ${focus ? 'bg-indigo-100' : ''}`
            }
          >
            {user.name}
          </ComboboxOption>
        ))}
      </ComboboxOptions>
    </Combobox>
  )
}

5. 使用 Field 组件统一表单体验

import { Field, Label, Description, Input, FieldError } from '@headlessui/react'
 
function LoginForm() {
  const [errors, setErrors] = useState({})
 
  return (
    <form>
      <Field>
        <Label className="text-sm font-medium text-gray-700">邮箱</Label>
        <Description className="text-sm text-gray-500">
          请输入注册时使用的邮箱
        </Description>
        <Input
          className="mt-1 block w-full rounded-lg border border-gray-300 px-3 py-2 shadow-sm"
          type="email"
          name="email"
          invalid={!!errors.email}
        />
        {errors.email && (
          <FieldError className="mt-1 text-sm text-red-600">
            {errors.email}
          </FieldError>
        )}
      </Field>
    </form>
  )
}

6. 服务端组件(RSC)兼容

Headless UI v2 组件可在 Next.js App Router 中使用,交互组件需标记为客户端组件:

// app/components/UserMenu.tsx
'use client'
 
import { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/react'
 
export function UserMenu() {
  return (
    <Menu>
      <MenuButton>用户菜单</MenuButton>
      <MenuItems anchor="bottom end">
        <MenuItem>
          <a href="/profile">个人资料</a>
        </MenuItem>
      </MenuItems>
    </Menu>
  )
}

技术局限与边界

已知的局限性

  1. 组件数量有限:仅 ~15 个核心组件,不覆盖 Accordion、Tooltip、DatePicker、Table 等复杂场景
    • 应对:结合其他库(如 React Aria)或使用 Tailwind UI 模板
  2. 无内置虚拟化:大型列表(如 1000+ 选项的 Select)无滚动虚拟化
    • 应对:结合 @tanstack/virtual 或自行实现
  3. Vue 版本更新节奏:Vue 版本可能比 React 版本更新稍慢
    • 社区曾有讨论(GitHub Discussion #3183)关于 React/Vue 功能对等的节奏问题
  4. 复杂数据表格无支持:不提供 Table/DataGrid 组件
    • 应对:使用 TanStack Table 等专用方案
  5. 无富文本编辑器支持:不涉及富文本领域
  6. 定位能力有限:内置的 anchor positioning 覆盖常见场景,但复杂级联定位(如嵌套子菜单)仍需额外处理
  7. 社区生态规模:相比 Radix UI 的社区插件和教程,Headless UI 的第三方资源相对较少

不适用的场景

  • ❌ 需要预制样式的快速原型(选择 shadcn/ui 或 Material UI)
  • ❌ 需要复杂数据表格(选择 TanStack Table)
  • ❌ 需要日期选择器(选择 React Day Picker 或 React Aria)
  • ❌ 需要拖拽排序(选择 dnd-kit 或 react-beautiful-dnd)
  • ❌ 纯展示组件库需求(Headless UI 只提供交互逻辑)

迁移注意事项

从其他库迁移到 Headless UI 时需注意:

  1. 样式需要全部重写:Headless UI 不提供任何样式,原来依赖的样式需要自行实现
  2. API 差异:与其他 headless 库的 API 不完全一致,需要逐一适配
  3. 无障碍行为差异:不同库对 ARIA 的实现细节可能不同,需重新测试

学习资源

官方资源

资源链接
官方文档(React)headlessui.com/react
官方文档(Vue)headlessui.com/vue
GitHub 仓库github.com/tailwindlabs/headlessui
v2.0 发布博客tailwindcss.com/blog/headless-ui-v2
Tailwind UI 模板tailwindui.com — 大量基于 Headless UI 的完整组件示例

推荐教程与文章

资源说明
Headless UI 官方组件示例每个组件都有完整的代码示例和交互演示
Tailwind UI 组件库付费但质量极高,展示 Headless UI 的最佳实践
Syntax.fm #751UI 组件库对比播客(含 Headless UI 讨论)
掘金:Radix UI vs ShadCN UI vs Headless UI中文社区深度对比文章

视频资源

资源说明
Syntax.fm - UI ComponentsShadCN / Tailwind UI / Headless / React Aria / Radix UI 对比
Headless UI vs Radix UI两个库的详细对比

社区

平台链接
GitHub Discussionsheadlessui/discussions
Tailwind CSS Discorddiscord.gg/tailwindcss — Headless UI 频道
Redditr/reactjsr/tailwindcss

2026 年现状

当前版本与路线图

截至 2026 年 7 月,Headless UI 处于 v2.2.x 版本阶段:

  • v2.2.8(2026 年 6 月):最新的补丁版本,修复了 Combobox 键盘交互等组件级问题
  • v2.1(2025 年):引入 Field、Label、Description、FieldError 等表单组件,以及 Button、Input、Textarea、Select 等无样式表单控件
  • v2.0(2024 年 5 月):重大更新,引入内置 anchor positioning、Checkbox 组件、表单集成

2026 年生态定位

  1. 稳定的核心库:Headless UI 已是 Tailwind 生态的事实标准交互组件库,与 Tailwind CSS 4 完全兼容
  2. shadcn/ui 的互补:shadcn/ui 在 2025 年增加了 Base UI 作为可选底层,Headless UI 保持独立的 primitives 定位,两者面向不同需求层
  3. Vue 生态的独特选择:仍然是主流 headless 库中唯一同时支持 React 和 Vue 的方案
  4. 持续维护状态:Tailwind Labs 团队持续进行 bug 修复和小幅改进,保持活跃但节奏稳定

市场份额

  • npm 周下载量约 200 万+,是 React/Vue 生态中使用量最大的 headless 组件库之一
  • 在 Tailwind CSS 用户中,Headless UI 是最常被搭配的交互组件库
  • 在 "headless UI library" 搜索中保持前三位的可见度

未来展望

  • 组件扩展:社区持续呼吁增加 Accordion、Tooltip、DatePicker 等组件,Tailwind Labs 可能通过 Tailwind UI(付费)或 Headless UI 逐步补充
  • React 19+ 支持:持续跟进 React 新特性(Server Actions 集成、Suspense 优化等)
  • Vue 3.x 对齐:保持 React 和 Vue 版本的功能对等
  • 定位能力增强:内置 anchor positioning 可能继续增强,支持更多复杂定位场景

文档更新日期:2026 年 7 月 7 日 信息来源:Headless UI 官网、GitHub 仓库、Tailwind CSS 官方博客、社区文章及 npm 数据