Headless UI:无样式、可访问的 UI 组件

Adam Wathan
Headless UI Logo

构建现代 Web 应用时最大的痛点之一是构建自定义组件,例如选择菜单、下拉菜单、切换按钮、模态框、选项卡、单选按钮组 - 这些组件在不同项目中非常相似,但又各不相同。

¥One of the biggest pain points when building modern web applications is building custom components like select menus, dropdowns, toggles, modals, tabs, radio groups — components that are pretty similar from project to project, but never quite the same.

你可以使用现成的软件包,但它们通常与自身提供的样式紧密耦合。最终,很难让它们与你自己项目的外观和风格相匹配,而且几乎总是需要编写一堆 CSS 覆盖,这在使用 Tailwind CSS 时感觉像是倒退了一大步。

¥You could use an off-the-shelf package, but they usually come tightly coupled with their own provided styles. It ends up being very hard to get them to match the look and feel of your own project, and almost always involves writing a bunch of CSS overrides, which feels like a big step backwards when working Tailwind CSS.

另一种选择是从头开始构建你自己的组件。乍一看似乎很简单,但随后你就会想起需要添加键盘导航、ARIA 属性管理、焦点捕获等功能,然后突然间,你得花 3-4 周的时间来构建一个真正万无一失的下拉菜单。

¥The other option is building your own components from scratch. At first it seems easy, but then you remember you need to add support for keyboard navigation, managing ARIA attributes, focus trapping, and all of a sudden you're spending 3-4 weeks trying to build a truly bullet-proof dropdown menu.

我们认为有更好的选择,所以我们正在构建它。

¥We think there's a better option, so we're building it.

Headless UI 是一组完全无样式、完全可访问的 React 和 Vue(以及即将推出的 Alpine.js)UI 组件,让你可以轻松构建这些自定义组件,无需担心任何复杂的实现细节,也无需牺牲使用简单的实用程序类从头开始设计样式的能力。

¥Headless UI is a set of completely unstyled, fully accessible UI components for React and Vue (and soon Alpine.js) that make it easy to build these sorts of custom components without worrying about any of the complex implementation details yourself, and without sacrificing the ability to style them from scratch with simple utility classes.

使用 @headlessui/react 构建自定义下拉菜单(库中包含的众多组件之一)的样子如下:完整支持键盘导航和 ARIA 属性管理,并使用简单的 Tailwind CSS 实用程序进行样式设置:

¥Here's what it looks like to build a custom dropdown (one of many components the library includes) using @headlessui/react, with complete keyboard navigation support and ARIA attribute management, styled with simple Tailwind CSS utilities:

import { Menu } from "@headlessui/react";
function MyDropdown() {
return (
<Menu as="div" className="relative">
<Menu.Button className="rounded bg-blue-600 px-4 py-2 text-white ...">Options</Menu.Button>
<Menu.Items className="absolute right-0 mt-1">
<Menu.Item>
{({ active }) => (
<a className={`${active && "bg-blue-500 text-white"} ...`} href="/account-settings">
Account settings
</a>
)}
</Menu.Item>
<Menu.Item>
{({ active }) => (
<a className={`${active && "bg-blue-500 text-white"} ...`} href="/documentation">
Documentation
</a>
)}
</Menu.Item>
<Menu.Item disabled>
<span className="opacity-75 ...">Invite a friend (coming soon!)</span>
</Menu.Item>
</Menu.Items>
</Menu>
);
}

以下是该示例中免费提供的内容,无需你自己编写任何相关代码:

¥Here's what you're getting for free in that example, without having to write a single line of code related to it yourself:

  • 点击、空格键、回车键或使用箭头键时,下拉面板会打开

    ¥The dropdown panel opens on click, spacebar, enter, or when using the arrow keys

  • 按下 Esc 键或点击下拉菜单外部时,下拉菜单会关闭

    ¥The dropdown closes when you press escape, or click outside of it

  • 你可以使用上下箭头键导航项目

    ¥You can navigate the items using the up and down arrow keys

  • 你可以使用 Home 键跳转到第一项,使用 End 键跳转到最后一项

    ¥You can jump the first item using the Home key, and the last item using the End key

  • 使用键盘导航时,禁用的项目会自动跳过

    ¥Disabled items are automatically skipped when navigating with the keyboard

  • 使用键盘导航后,将鼠标悬停在某个项目上将切换到基于鼠标位置的聚焦。

    ¥Hovering over an item with your mouse after navigating with the keyboard will switch to mouse position based focusing

  • 使用键盘导航时,项目会正确地向屏幕阅读器朗读。

    ¥Items are announced properly to screen readers while navigating them with the keyboard

  • 下拉按钮会正确地向屏幕阅读器显示为控制菜单

    ¥The dropdown button is properly announced to screenreaders as controlling a menu

  • ……可能还有更多我忘记的东西。

    ¥...and probably tons more that I'm forgetting.

所有这些都无需在代码中的任何位置写入 aria 字母,也无需编写任何事件监听器。而且你仍然可以完全控制设计!

¥All without writing the letters aria anywhere in your own code, and without writing a single event listener. And you still have complete control over the design!

这是 此组件超过 3000 行测试代码。你不必自己动手,这真是太棒了,对吧?

¥There are over 3000 lines of tests for this component. Pretty nice that you didn't have to do that yourself, right?

以下是一个完整样式的现场演示(取自 Tailwind UI),你可以看到它的实际效果:

¥Here's a fully-styled live demo (taken from Tailwind UI) so you can see it in action:

请务必使用键盘或屏幕阅读器进行尝试,以真正体会它的魅力!

¥Make sure to try it with the keyboard or a screen reader to really appreciate it!

我们刚刚标记了 v0.2.0 版本,目前包含以下组件:

¥We just tagged v0.2.0, which currently includes the following components:

要了解更多信息并深入研究,请执行 前往 Headless UI 网站 操作并阅读文档。

¥To learn more and dive in, head over to the Headless UI website and read the documentation.


如果你过去几年一直关注我的在线工作,你可能还记得我对 无渲染 UI 组件 的迷恋 - 我真正开始关注 2017 年底 的时候。多年来我一直渴望拥有这样一个库,但在我们开始壮大团队之前,我们一直缺乏实现它的资源。

¥If you've followed my work online for the last few years, you might remember my fascination with renderless UI components — something I was really started getting into towards the end of 2017. I've wanted a library like this to exist for years, but until we started growing the team we just didn't have the resources to make it happen.

今年早些时候,我们聘请了 Robin Malfait 就一直全职致力于 Headless UI 的开发。

¥Earlier this year we hired Robin Malfait, and he's been working on Headless UI full-time ever since.

我们开展这个项目的最大动机是,我们非常希望为 Tailwind UI 添加可用于生产的 JS 示例,而 Tailwind UI 目前是一个纯 HTML 代码、自带 JavaScript 的项目。对于许多希望完全控制一切运作方式的客户来说,这非常棒,但对于许多其他客户来说,这是一个摩擦点。

¥The biggest motivation for this project is that we'd really like to add production-ready JS examples to Tailwind UI, which is currently an HTML-only, bring-your-own-JavaScript sort of project. This is great for lots of our customers who want full control over how everything works, but for many others it's a point of friction.

我们不想在每个组件示例中都添加 200 行繁琐的 JS 代码,所以我们开始研究 Headless UI,以此来消除所有这些干扰,同时又不牺牲实际 UI 设计中的任何灵活性。

¥We didn't want to add 200 lines of gnarly JS to every component example, so we started working on Headless UI as a way to extract all of that noise, without giving up any flexibility in the actual UI design.

为什么要重新发明轮子?(Why reinvent the wheel?)

¥Why reinvent the wheel?

我们并非首批尝试解决此问题的人。下移按钮 是 2017 年我看到的第一个让我对这个想法感到兴奋的库,Reach UIReakit 于 2018 年开始开发,而 React Aria 是最近发布的,就在今年早些时候。

¥We're not the first people to try and tackle this problem. Downshift was the first library I saw that got me excited about this idea back in 2017, Reach UI and Reakit started development in 2018, and React Aria was released most recently, just earlier this year.

我们决定尝试自己解决这个问题,原因如下:

¥We decided to try our own take on the problem for a few reasons:

  • 现有的解决方案几乎完全集中在 React 上,我们希望将这些想法带到其他生态系统,例如 Vue、Alpine,并希望未来能扩展到更多生态系统。

    ¥Existing solutions are focused almost entirely on React, and we'd like to bring these ideas to other ecosystems like Vue, Alpine, and hopefully more in the future.

  • 这些库将成为向 Tailwind UI 添加 JS 支持的基础,而这正是业务持续运转的关键,因此,拥有对这些库如何工作以及它们支持哪些功能的完全决策权至关重要。

    ¥These libraries are going to be foundational for adding JS support to Tailwind UI, and since that's what keeps the business running it felt important to have complete decision-making power over how the libraries worked and what they supported.

  • 我们对于这些组件的 API 应该是什么样子有自己的想法,并且希望能够自由地探索这些想法。

    ¥We have our own ideas on what the APIs should look like for these components, and want to be able to explore those ideas freely.

  • 我们希望确保使用 Tailwind 为这些组件设计样式始终非常容易,而无需编写自定义 CSS。

    ¥We want to make sure it is always super easy to style these components with Tailwind, rather than having to write custom CSS.

我们认为我们目前提出的方案在灵活性和开发者体验之间取得了良好的平衡,我们也非常感激有其他人在解决类似的问题,我们可以向他们学习并分享我们的想法。

¥We think what we've come up with so far hits a great balance between flexibility and developer experience, and we're grateful there are other people working on similar problems that we can learn from and share our ideas with.

下一步是什么(What's next)

¥What's next

我们还有很多组件需要为 Headless UI 开发,包括:

¥We've got quite a few more components to develop for Headless UI, including:

  • 模态框

    ¥Modal

  • 单选按钮组

    ¥Radio group

  • 标签页

    ¥Tabs

  • 手风琴

    ¥Accordion

  • 组合框

    ¥Combobox

  • 日期选择器

    ¥Datepicker

...可能还有更多。我们希望能够在年底前为 React、Vue 和 Alpine 开发 v1.0 版本。

¥...and likely many more. We're also about to start on Alpine.js support, and we're hoping to be able to tag a v1.0 for React, Vue, and Alpine near the end of the year.

之后,我们将开始探索其他框架,希望最终能够为 Svelte、Angular 和 Ember 等生态系统提供相同的工具,无论是一流的还是与社区合作伙伴合作的。

¥After that we'll start exploring other frameworks, with the hope that we can eventually offer the same tools for ecosystems like Svelte, Angular, and Ember, either first-class or with community partners.

如果你想跟上我们的步伐,请务必使用 在 GitHub 上关注该项目

¥If you'd like to keep up with what we're doing, be sure to follow the project on GitHub.

想要讨论这篇文章吗?Discuss this on GitHub →

¥Want to talk about this post? Discuss this on GitHub →

TailwindCSS v4.1 中文网 - 粤ICP备13048890号