
当涉及到寻找改进方法时,没有什么比使用自己的工具构建真实的东西更有效了。
在过去的几个月中,当我们一直在开发 Catalyst 时,我们对 Headless UI 进行了数十项改进,让你可以编写更少的代码,并使开发人员的体验更好。
我们刚刚发布了用于 React 的 Headless UI v2.0,这是所有这些工作的结晶。
以下是所有最好的新功能
通过从 npm 安装最新版本的 @headlessui/react
将其添加到你的项目中
npm install @headlessui/react@latest
如果你是从 v1.x 升级,请查看 升级指南 以了解有关更改的更多信息。
内置锚点定位
我们已将 Floating UI 直接集成到 Headless UI 中,因此你永远不必担心下拉菜单超出视图或被屏幕上的其他元素遮挡。
在 Menu
、Popover
、Combobox
和 Listbox
组件上使用新的 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 变量来在不同的断点调整样式。
查看每个组件的 锚点定位文档,了解所有详细信息。
新的复选框组件
我们添加了一个新的无头 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> );}
复选框可以是受控的或不受控的,并且可以自动将其状态与隐藏的输入同步,以便与 HTML 表单良好配合。
请查看 复选框文档 以了解更多信息。
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> );}
新的 Field
和 Fieldset
组件还像原生的 <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 个新组件 — Fieldset
、Legend
、Field
、Label
、Description
、Input
、Select
和 Textarea
。
有关更多详细信息,请从 Fieldset 文档 开始,然后逐步浏览其余文档。
改进的悬停、焦点和活动状态检测
使用来自出色的 React Aria 库的底层钩子,Headless UI 现在向你的控件添加更智能的 data-*
状态属性,这些属性在不同设备上的行为比原生 CSS 伪类更一致
data-active
— 类似于:active
,但在拖动元素时会移除。data-hover
— 类似于:hover
,但在触摸设备上会被忽略,以避免粘性悬停状态。-
data-focus
— 类似于:focus-visible
,没有来自强制聚焦的误报。
单击、悬停、聚焦和拖动按钮以查看应用的数据属性
要了解为什么使用 JavaScript 应用这些样式很重要,我强烈建议你阅读 Devon Govett 关于此主题的优秀博客系列
Web 永远不会让我对实际制作精美事物所需付出的努力感到惊讶。
组合框列表虚拟化
我们已将 TanStack Virtual 集成到 Headless UI 中,以在你需要在组合框中放置 10 万个项目时支持列表虚拟化,因为,嘿,这是老板让你做的。
使用新的 virtual
属性传入你的所有项目,并使用 ComboboxOptions
渲染属性为单个选项提供模板
打开组合框并滚动浏览 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> );}
查看新的 虚拟滚动文档 以了解更多信息。
新网站和改进的文档
为了配合此次重大发布,我们还对文档进行了重大改进,并为网站赋予了全新的面貌

前往新的 headlessui.com 查看!