Headless UI v2.0 for React

Adam Wathan
Jonathan Reinink
Headless UI v2.0

没有什么比实际使用自己的工具构建真实的东西更能找到改进事物的方法了。

在过去几个月我们致力于 Catalyst 的开发过程中,我们对 Headless UI 进行了数十项改进,让您编写更少的代码,并使开发者体验更加出色。

我们刚刚发布了 Headless UI v2.0 for React,这是所有这些工作的结晶。

以下是所有最好的新功能

通过从 npm 安装最新版本的 @headlessui/react 将其添加到您的项目中

npm install @headlessui/react@latest

如果您是从 v1.x 升级,请查看升级指南,以了解更多关于更改的内容。


内置锚点定位

我们已将 Floating UI 直接集成到 Headless UI 中,因此您永远不必担心下拉菜单超出视图或被屏幕上的其他元素遮挡。

MenuPopoverComboboxListbox 组件上使用新的 anchor 属性来指定锚点定位,然后使用 CSS 变量(如 --anchor-gap--anchor-padding)微调位置

向上和向下滚动以查看下拉菜单位置的变化

import { Menu, MenuButton, MenuItem, MenuItems } from "@headlessui/react";
function Example() {
return (
<Menu>
<MenuButton>Options</MenuButton>
<MenuItems
anchor="bottom start"
className="[--anchor-gap:8px] [--anchor-padding:8px]"
>
<MenuItem>
<button>Edit</button>
</MenuItem>
<MenuItem>
<button>Duplicate</button>
</MenuItem>
<hr />
<MenuItem>
<button>Archive</button>
</MenuItem>
<MenuItem>
<button>Delete</button>
</MenuItem>
</MenuItems>
</Menu>
);
}

这个 API 真正出色之处在于,您可以通过使用类似 sm:[--anchor-gap:4px] 的实用程序类更改 CSS 变量,从而在不同的断点调整样式。

查看每个组件的锚点定位文档,了解所有详细信息。


新的复选框组件

我们添加了一个新的 headless Checkbox 组件来补充我们现有的 RadioGroup 组件,从而可以轻松构建完全自定义的复选框控件

这将使您可以提前访问我们正在开发的任何超棒的新功能。

import { Checkbox, Description, Field, Label } from "@headlessui/react";
import { CheckmarkIcon } from "./icons/checkmark";
import clsx from "clsx";
function Example() {
return (
<Field>
<Checkbox
defaultChecked
className={clsx(
"size-4 rounded border bg-white dark:bg-white/5",
"data-[checked]:border-transparent data-[checked]:bg-blue-500",
"focus:outline-none data-[focus]:outline-2 data-[focus]:outline-offset-2 data-[focus]:outline-blue-500",
)}
>
<CheckmarkIcon className="stroke-white opacity-0 group-data-[checked]:opacity-100" />
</Checkbox>
<div>
<Label>Enable beta features</Label>
<Description>This will give you early access to any awesome new features we're developing.</Description>
</div>
</Field>
);
}

复选框可以是受控的或非受控的,并且可以自动将其状态与隐藏的 input 同步,以便与 HTML 表单良好配合。

查看 Checkbox 文档以了解更多信息。


HTML 表单组件

我们添加了一整套新组件,它们只是包装了原生表单控件,但会自动完成连接 ID 和 aria-* 属性的所有繁琐工作。

以下是在之前构建一个带有正确关联的 <label> 和描述的简单 <input> 字段的样子

<div>
<label id="name-label" for="name-input">
Name
</label>
<input id="name-input" aria-labelledby="name-label" aria-describedby="name-description" />
<p id="name-description">Use your real name so people will recognize you.</p>
</div>

这是在 Headless UI v2.0 中使用这些新组件的样子

import { Description, Field, Input, Label } from "@headlessui/react";
function Example() {
return (
<Field>
<Label>Name</Label>
<Input name="your_name" />
<Description>Use your real name so people will recognize you.</Description>
</Field>
);
}

新的 FieldFieldset 组件还像原生 <fieldset> 元素一样级联禁用状态,因此您可以轻松地一次禁用整组控件

选择一个国家/地区以查看区域字段变为启用状态

配送详情

我们目前仅运送到北美。

import { Button, Description, Field, Fieldset, Input, Label, Legend, Select } from "@headlessui/react";
import { regions } from "./countries";
export function Example() {
const [country, setCountry] = useState(null);
return (
<form action="/shipping">
<Fieldset>
<Legend>Shipping details</Legend>
<Field>
<Label>Street address</Label>
<Input name="address" />
</Field>
<Field>
<Label>Country</Label>
<Description>We currently only ship to North America.</Description>
<Select name="country" value={country} onChange={(event) => setCountry(event.target.value)}>
<option></option>
<option>Canada</option>
<option>Mexico</option>
<option>United States</option>
</Select>
</Field>
<Field disabled={!country}>
<Label className="data-[disabled]:opacity-40">State/province</Label>
<Select name="region" className="data-[disabled]:opacity-50">
<option></option>
{country && regions[country].map((region) => <option>{region}</option>)}
</Select>
</Field>
<Button>Submit</Button>
</Fieldset>
</form>
);
}

我们使用渲染的 HTML 中的 data-disabled 属性公开禁用状态。这使我们甚至可以在不支持原生 disabled 属性的元素(如关联的 <label> 元素)上公开它,从而可以非常轻松地微调每个元素的禁用样式。

总而言之,我们在这里添加了 8 个新组件 — FieldsetLegendFieldLabelDescriptionInputSelectTextarea

有关更多详细信息,请从 Fieldset 文档开始,然后逐步了解其余内容。


改进的悬停、焦点和激活状态检测

Headless UI 现在使用来自出色的 React Aria 库的 hooks,在底层为您的控件添加更智能的 data-* 状态属性,这些属性在不同设备上的行为比原生 CSS 伪类更一致

  • data-active — 类似于 :active,但在从元素上拖动时会被移除。
  • data-hover — 类似于 :hover,但在触摸设备上会被忽略,以避免粘滞悬停状态。
  • data-focus — 类似于 :focus-visible,没有来自命令式聚焦的误报。

单击、悬停、聚焦和拖动按钮以查看应用的数据属性

要了解更多关于为什么使用 JavaScript 应用这些样式很重要,我强烈建议您阅读 Devon Govett 关于此主题的优秀博客系列

Web 总是让我惊讶,为了真正做出美好的事物需要付出如此多的努力。


Combobox 列表虚拟化

我们已将 TanStack Virtual 集成到 Headless UI 中,以支持列表虚拟化,当您需要在组合框中放入 10 万个项目时,因为,嘿,这就是老板告诉您要做的。

使用新的 virtual 属性传入您的所有项目,并使用 ComboboxOptions render 属性为单个选项提供模板

打开组合框并滚动浏览 1,000 个选项

import { Combobox, ComboboxButton, ComboboxInput, ComboboxOption, ComboboxOptions } from "@headlessui/react";
import { ChevronDownIcon } from "@heroicons/react/20/solid";
import { useState } from "react";
const people = [
{ id: 1, name: "Rossie Abernathy" },
{ id: 2, name: "Juana Abshire" },
{ id: 3, name: "Leonel Abshire" },
{ id: 4, name: "Llewellyn Abshire" },
{ id: 5, name: "Ramon Abshire" },
// ...up to 1000 people
];
function Example() {
const [query, setQuery] = useState("");
const [selected, setSelected] = useState(people[0]);
const filteredPeople =
query === ""
? people
: people.filter((person) => {
return person.name.toLowerCase().includes(query.toLowerCase());
});
return (
<Combobox
value={selected}
virtual={{ options: filteredPeople }}
onChange={(value) => setSelected(value)}
onClose={() => setQuery("")}
>
<div>
<ComboboxInput displayValue={(person) => person?.name} onChange={(event) => setQuery(event.target.value)} />
<ComboboxButton>
<ChevronDownIcon />
</ComboboxButton>
</div>
<ComboboxOptions>
{({ option: person }) => (
<ComboboxOption key={person.id} value={person}>
{person.name}
</ComboboxOption>
)}
</ComboboxOptions>
</Combobox>
);
}

查看新的虚拟滚动文档以了解更多信息。


新网站和改进的文档

为了配合这次重大发布,我们还大幅改进了文档,并为网站焕然一新

New Headless UI v2.0 website

访问新的 headlessui.com 查看一下!

直接在您的收件箱中获取我们的所有更新。
注册我们的新闻通讯。

Copyright © 2025 Tailwind Labs Inc.·商标政策