我已经有一段时间没有写关于我们正在做的事情了,所以有很多东西要分享!老实说太多了——我发布此更新的主要动力是,我们下周还有更多内容要发布,而且我觉得在我分享所有已经发布的内容之前,我不能分享那些内容。
所以穿上你的泳衣,坐在你的躺椅上,准备好吸收一些 CSS 维生素。
Headless UI v1.6 发布了
几周前,我们发布了 Headless UI 的新次要版本,这是一个非样式化的 UI 库,我们构建它以使 Tailwind UI 可以添加 React 和 Vue 支持。
查看发行说明以获取所有详细信息,但以下是一些亮点。
多选支持
我们在 Combobox
和 Listbox
组件中都添加了一个新的 multiple
属性,以便人们可以选择多个选项。
只需添加 multiple
属性并将数组绑定为您的 value
,即可开始使用
function MyCombobox({ items }) { const [selectedItems, setSelectedItems] = useState([]); return ( <Combobox value={selectedItems} onChange={setSelectedItems} multiple> {selectedItems.length > 0 && ( <ul> {selectedItems.map((item) => ( <li key={item}>{item}</li> ))} </ul> )} <Combobox.Input /> <Combobox.Options> {items.map((item) => ( <Combobox.Option key={item} value={item}> {item} </Combobox.Option> ))} </Combobox.Options> </Combobox> );}
查看 combobox 文档 和 listbox 文档 以了解更多信息。
可为空的组合框
在 v1.6 之前,如果您删除组合框的内容并切换离开,它将恢复先前选择的选项。这在很多时候都是有道理的,但有时您确实想要清除组合框的值。
我们添加了一个新的 nullable
属性,使这成为可能——只需添加该属性,现在您就可以删除该值,而不会恢复先前的值
function MyCombobox({ items }) { const [selectedItem, setSelectedItem] = useState([]); return ( <Combobox value={selectedItem} onChange={setSelectedItem} nullable> <Combobox.Input /> <Combobox.Options> {items.map((item) => ( <Combobox.Option key={item} value={item}> {item} </Combobox.Option> ))} </Combobox.Options> </Combobox> );}
简易 HTML 表单支持
现在,如果您向表单组件(如 Listbox
、Combobox
、Switch
和 RadioGroup
)添加 name
属性,我们将自动创建一个隐藏输入,该输入与组件的值同步。
这使得使用常规表单提交或使用 Remix 中的 <Form>
组件等方式将数据发送到服务器变得非常容易。
<form action="/projects/1/assignee" method="post"> <Listbox value={selectedPerson} onChange={setSelectedPerson} name="assignee" > {/* ... */} </Listbox> <button>Submit</button></form>
这适用于数字和字符串等简单值,也适用于对象——我们使用 1996 年的方括号表示法自动将它们序列化为多个字段
<input type="hidden" name="assignee[id]" value="1" /><input type="hidden" name="assignee[name]" value="Durward Reynolds" />
如果您想再次阅读我刚才写的所有内容,但在不同的域上,请查看文档。
可滚动对话框的改进
对话框实际上是地球上最难构建的东西。 一段时间以来,我们一直在与棘手的 滚动 问题作斗争,并且认为我们最终在 v1.6 中解决了所有问题。
关键在于我们更改了“单击外部关闭”的工作方式。我们过去使用这个 Dialog.Overlay
组件,您将其放在实际对话框的后面,并且我们在其上有一个单击处理程序,单击时会关闭对话框。实际上,我非常喜欢这种原则的简单性——检测特定元素何时被单击比检测何时单击除了特定元素之外的任何内容要少得多,尤其是在您的对话框内部渲染了其他内容的情况下,这些内容本身正在门户网站和其他东西中渲染其他内容。
这种方法的问题是,如果您有一个需要滚动的长对话框,您的覆盖层将位于滚动条的顶部,并且尝试单击滚动条将关闭对话框。这不是你想要的!
因此,为了以非破坏性的方式解决此问题,我们添加了一个新的 Dialog.Panel
组件供您替代使用,现在,每当您单击该组件外部时,我们都会关闭对话框,而不是在单击覆盖层时专门关闭对话框
<Dialog open={isOpen} onClose={closeModal} className="fixed inset-0 flex items-center justify-center ..."> <Dialog.Overlay className="fixed inset-0 bg-black/25" /> <div className="fixed inset-0 bg-black/25" /> <div className="bg-white shadow-xl rounded-2xl ..."> <Dialog.Panel className="bg-white shadow-xl rounded-2xl ..."> <Dialog.Title>Payment successful</Dialog.Title> {/* ... */} </div> </Dialog.Panel></Dialog>
查看更新后的对话框文档,了解使用新的面板组件而不是覆盖层的更完整示例。
更好的焦点捕获
对话框是地球上最难构建的东西的众多原因之一是焦点捕获。我们对此的第一次尝试涉及劫持 Tab 键并手动聚焦下一个/上一个元素,以便当您到达末尾时,我们可以循环回到焦点陷阱中的第一个项目。
在人们开始在焦点陷阱内部使用门户网站之前,这工作正常。现在基本上不可能管理了,因为您可以 Tab 到日期选择器或概念上在对话框内部的东西,但实际上不是,因为它出于样式原因在门户网站中呈现。
Robin 提出了一个 非常酷的解决方案,它非常简单——与其尝试手动控制 Tab 键的工作方式,不如在焦点陷阱的开头和结尾分别抛出一个不可见的、可聚焦的元素。现在,每当这些哨兵元素之一接收到焦点时,您只需将焦点移动到它实际应该在的位置,这取决于您是在第一个元素还是最后一个元素,以及用户是向前还是向后 Tab。
使用这种方法,您根本不必劫持 Tab 键——您只需让浏览器完成所有工作,并且仅在您的哨兵元素之一接收到焦点时才手动移动焦点。
在弄清楚这一点后,我们注意到其他几个库已经在做同样的事情,所以这没什么突破性或新颖的,但我认为这非常聪明,值得与任何没有想到这种技术的人分享。
Tailwind UI 的团队管理功能
当我们首次发布 Tailwind UI 时,“团队”只有我和 Steve,因此,如果我们想让我们两个人实际完成这项工作,我们必须保持很多事情的简单性。
其中之一是团队许可。我们没有提供任何花哨的团队成员邀请流程或其他任何东西,我们只是要求人们与他们的团队共享他们的 Tailwind UI 凭据。这足以让我们完成工作,因为 Tailwind UI 实际上并没有以用户特定的方式做任何事情,而且您的团队的每个成员都获得相同的体验。
另外,对我们来说,必须获取团队中每个人的电子邮件地址,将它们输入到某个表单中,向每个人发送邀请电子邮件,并让他们接受邀请感觉像是行政地狱,尤其是当每个人在登录后都获得相同的体验时。
但与此同时,共享任何凭据都相当低端,这不是我们引以为豪的设计决策。我在 Tailwind UI 上使用的密码 (slayerfan1234
) 与我在我的银行帐户上使用的密码相同——我不想与任何人分享!
因此,几周前,我们决定弄清楚并构建一些东西。

我们最终确定的是一个纯粹基于链接的邀请系统,您只需复制您的邀请链接,在 Slack/Discord/任何地方与您的团队共享,并在需要时重置您的链接。您还可以授予人们“成员”或“所有者”权限,这控制他们是否可以管理团队成员或查看账单历史记录。
这使得邀请您的团队变得非常容易,无需进行大量繁琐的数据输入,并且可以在 UI 中撤销访问权限(如果有人离开),而不是通过更改您的共享密码。
现在,任何拥有 Tailwind UI 团队帐户的人都可以使用此功能——只需打开下拉菜单并单击“我的团队”即可命名您的团队并开始邀请您的同事。
您可以在 Tailwind UI 网站上为您的团队购买许可证,或者如果您拥有个人许可证并想开始与您的团队一起使用 Tailwind UI,则可以升级到团队许可证。
在 Tailwind UI 中将 Vue 示例更新为 <script setup>
自从发布 Tailwind UI 的 Vue 支持以来,Vue 3 中的新 <script setup>
语法已成为编写单文件组件的推荐方式。
我们已更新 Tailwind UI 中的所有 Vue 示例以使用这种新格式,这减少了大量样板代码
<template> <Listbox as="div" v-model="selected"> <!-- ... --> </Listbox></template><script setup> import { ref } from "vue"; import { Listbox, ListboxButton, ListboxLabel, ListboxOption, ListboxOptions } from "@headlessui/vue"; import { CheckIcon, SelectorIcon } from "@heroicons/vue/solid"; const people = [ { id: 1, name: "Wade Cooper" }, // ... ]; const selected = ref(people[3]);</script>
对我来说,绝对最好的部分是您不必再在 components
下显式注册任何内容——范围内的任何组件都可以自动用于模板。
使用 <script setup>
还允许您使用命名空间组件,例如 Listbox.Button
,就像我们在 Headless UI 的 React 版本中所做的那样。我们尚未更新 Headless UI 以这种方式公开组件,但我们可能会很快这样做,这将使您减少大量导入。
用于 VS Code 的新 Tailwind CSS 语言模式
Tailwind 使用许多非标准的 at 规则,例如 @tailwind
和 @apply
,因此如果您使用常规 CSS 语言模式,您会在 VS Code 中收到 lint 警告。
为了解决这个问题,我们一直建议人们使用 PostCSS 语言支持插件,它可以消除这些警告,但也删除了所有其他 CSS IntelliSense 支持。
因此,几周前,我们发布了第一方 Tailwind CSS 语言模式,作为我们的 Tailwind CSS IntelliSense 扩展的一部分,它构建在内置 CSS 语言模式之上,以添加特定于 Tailwind 的语法突出显示并修复您通常会看到的 lint 警告,而不会丢失您确实想要保留的任何 CSS IntelliSense 功能。

通过下载最新版本的 Tailwind CSS IntelliSense 并选择“Tailwind CSS”作为 CSS 文件的语言模式来试用它。
Tailwind Play 中的“生成的 CSS”面板
在过去的几个月中,我们对 Tailwind Play 进行了许多小的改进,我最喜欢的是新的“生成的 CSS”面板。

它向您显示从 HTML 生成的所有 CSS,并允许您按层进行过滤,这对于故障排除非常有用。在内部,我们一直使用它来调试类未被检测到的奇怪问题,以便我们可以执行任何必要的可怕的正则表达式手术以使其工作。
我们还为每个窗格添加了一个“整理”按钮 (Cmd + S),它将自动格式化您的代码(并对您的类进行排序!)和一个“复制”按钮 (Cmd + A Cmd + C,但您已经知道了)。
重新设计 Refactoring UI 网站
当我们在 2018 年 12 月发布 Refactoring UI 时,Steve 和我实际上是在发布前一天晚上凌晨 1 点设计并构建了最终的着陆页。
发生的事情是我们设计了这个性感的着陆页,然后我正在撰写公告电子邮件以发送给我们邮件列表上的每个人,我们都认为“伙计,这封电子邮件中的内容很棒,并且比我们在这个着陆页设计中拥有的内容更引人注目”。
但是该内容实际上并不适合我们设计的内容,因此在最后一刻,我们放弃了我们设计的所有内容,并根据新内容拼凑了一个简单得多的页面。它看起来还不错,但它不是我们真正想要的超级漂亮的体验。
因此,几周前,我们决定最终设计一些新的东西。

我仍然为这本书感到非常自豪——可能比我们制作过的任何东西都更自豪。它在 Goodreads 上的评分为 4.68,拥有超过 1100 个评分和近 200 条评论,这对于一本自出版的电子书来说,对我来说感觉非常不可思议。
期待有一天发布第二个版本,其中包含我们此后学到的一切!
Tailwind CSS 模板即将推出
我们在 Twitter 上稍微预告了一下,但在过去的几个月中,我们一直在努力开发大量成熟的 Tailwind CSS 网站模板。
这是其中一个模板的预览——一个使用 Next.js 和 Stripe 的新 Markdoc 库构建的文档站点模板

我对发布这些模板感到异常兴奋。我为 Tailwind UI 作为产品感到非常自豪,但可复制粘贴的代码片段格式的局限性之一是,我们没有机会真正向您展示如何组件化事物、最大限度地减少重复以及将事物构建为完整的、可用于生产的网站。
我们现在正在开发的模板将非常擅长填补这一空白。除了获得漂亮的模板作为您自己项目的起点之外,您还可以深入研究代码并准确了解我们自己如何使用 Tailwind CSS 构建网站。
我们尚未确定这些模板的确切发布日期,但我们希望在下个月发布一些内容。当我们取得更多进展时,将分享更多信息!