开关 (切换)

开关是用于在两种状态之间切换值的良好界面,并提供与原生复选框元素相同的语义和键盘导航。

要开始,请通过 npm 安装无状态 UI

npm install @headlessui/react

开关使用 Switch 组件构建。您可以通过直接单击组件或在组件获得焦点时按下空格键来切换开关。

切换开关会调用 onChange 函数,并传入 checked 值的否定版本。

import { useState } from 'react' import { Switch } from '@headlessui/react' function MyToggle() { const [enabled, setEnabled] = useState(false) return ( <Switch checked={enabled} onChange={setEnabled} className={`${ enabled ? 'bg-blue-600' : 'bg-gray-200' } relative inline-flex h-6 w-11 items-center rounded-full`} > <span className="sr-only">Enable notifications</span> <span className={`${ enabled ? 'translate-x-6' : 'translate-x-1' } inline-block h-4 w-4 transform rounded-full bg-white transition`} /> </Switch> ) }

无状态 UI 会跟踪每个组件的大量状态,例如当前选择了哪个开关选项,弹出窗口是打开还是关闭,或者菜单中哪个项目当前通过键盘处于活动状态。

但由于组件是无状态的,并且在开箱即用时完全没有样式,所以您无法看到此信息,直到您自己提供每个状态所需的样式。

每个组件都通过 渲染道具 公开其当前状态的信息,您可以使用这些信息来有条件地应用不同的样式或渲染不同的内容。

例如,Switch 组件公开了一个 checked 状态,它告诉您开关当前是否选中。

import { useState, Fragment } from 'react' import { Switch } from '@headlessui/react' function MyToggle() { const [enabled, setEnabled] = useState(false) return ( <Switch checked={enabled} onChange={setEnabled} as={Fragment}>
{({ checked }) => (
/* Use the `checked` state to conditionally style the button. */ <button className={`${
checked ? 'bg-blue-600' : 'bg-gray-200'
}
relative inline-flex h-6 w-11 items-center rounded-full`
}
>
<span className="sr-only">Enable notifications</span> <span className={`${
checked ? 'translate-x-6' : 'translate-x-1'
}
inline-block h-4 w-4 transform rounded-full bg-white transition`
}
/>
</button> )} </Switch> ) }

有关每个组件的完整渲染道具 API,请参阅 组件 API 文档

每个组件还通过 data-headlessui-state 属性公开其当前状态的信息,您可以使用这些信息来有条件地应用不同的样式。

渲染道具 API 中的任何状态为 true 时,它们将作为以空格分隔的字符串列出,因此您可以使用 CSS 属性选择器[attr~=value] 的形式对其进行定位。

例如,以下是 Switch 组件在开关选中时呈现的内容

<!-- Rendered `Switch` --> <button data-headlessui-state="checked"></button>

如果您使用的是 Tailwind CSS,您可以使用 @headlessui/tailwindcss 插件来使用修饰符(如 ui-checked:*)定位此属性

import { useState } from 'react' import { Switch } from '@headlessui/react' function MyToggle() { const [enabled, setEnabled] = useState(false) return ( <Switch checked={enabled} onChange={setEnabled}
className="relative inline-flex h-6 w-11 items-center rounded-full ui-checked:bg-blue-600 ui-not-checked:bg-gray-200"
>
<span className="sr-only">Enable notifications</span>
<span className="inline-block h-4 w-4 transform rounded-full bg-white transition ui-checked:translate-x-6 ui-not-checked:translate-x-1" />
</Switch> ) }

默认情况下,开关会渲染一个 button 以及您传递给它的任何子元素。这会使实现某些 UI 变得更加困难,因为子元素将嵌套在按钮中。

在这些情况下,您可以使用 Switch.Label 组件来获得更大的灵活性。

此示例演示了如何使用 Switch.GroupSwitchSwitch.Label 组件将标签渲染为按钮的同级元素。请注意,Switch.LabelSwitch 组件一起使用,并且它们都必须在父 Switch.Group 组件内渲染。

import { useState } from 'react' import { Switch } from '@headlessui/react' function MyToggle() { const [enabled, setEnabled] = useState(false) return (
<Switch.Group>
<div className="flex items-center">
<Switch.Label className="mr-4">Enable notifications</Switch.Label>
<Switch checked={enabled} onChange={setEnabled} className={`${ enabled ? 'bg-blue-600' : 'bg-gray-200' } relative inline-flex h-6 w-11 items-center rounded-full transition-colors focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2`} > <span className={`${ enabled ? 'translate-x-6' : 'translate-x-1' } inline-block h-4 w-4 transform rounded-full bg-white transition-transform`} /> </Switch> </div>
</Switch.Group>
) }

默认情况下,单击 Switch.Label 将切换开关,就像原生 HTML 复选框中的标签一样。如果您希望标签不可点击(如果您觉得这对于您的设计没有意义,您可能需要这样做),您可以在 Switch.Label 组件中添加一个 passive 道具

import { useState } from 'react' import { Switch } from '@headlessui/react' function MyToggle() { const [enabled, setEnabled] = useState(false) return ( <Switch.Group>
<Switch.Label passive>Enable notifications</Switch.Label>
<Switch checked={enabled} onChange={setEnabled}> {/* ... */} </Switch> </Switch.Group> ) }

如果您在开关中添加 name 道具,则会渲染一个隐藏的 input 元素,并与开关状态保持同步。

import { useState } from 'react' import { Switch } from '@headlessui/react' function Example() { const [enabled, setEnabled] = useState(true) return ( <form action="/notification-settings" method="post">
<Switch checked={enabled} onChange={setEnabled} name="notifications">
{/* ... */} </Switch> <button>Submit</button> </form> ) }

这使您可以在原生 HTML <form> 中使用开关,并像使用原生 HTML 表单控件一样进行传统的表单提交。

默认情况下,当开关选中时,值为 'on',而当开关未选中时,值为 'off'

<input type="hidden" name="notifications" value="on" />

您可以在需要时使用 value 道具自定义值

import { useState } from 'react' import { Switch } from '@headlessui/react' function Example() { const [enabled, setEnabled] = useState(true) return ( <form action="/accounts" method="post"> <Switch checked={enabled} onChange={setEnabled}
name="terms"
value="accept"
>
{/* ... */} </Switch> <button>Submit</button> </form> ) }

然后,隐藏的输入将使用您的自定义值(当开关选中时)

<input type="hidden" name="terms" value="accept" />

如果您在 Switch 中提供 defaultChecked 道具而不是 checked 道具,无状态 UI 将为您内部跟踪其状态,允许您将其用作 非受控组件

您可以通过 Switch 组件上的 checked 渲染道具访问当前状态。

import { Fragment } from 'react' import { Switch } from '@headlessui/react' function Example() { return ( <form action="/accounts" method="post">
<Switch name="terms-of-service" defaultChecked={true} as={Fragment}>
{({ checked }) => ( <button className={`${ checked ? 'bg-blue-600' : 'bg-gray-200' } relative inline-flex h-6 w-11 items-center rounded-full`} > <span className="sr-only">Enable notifications</span> <span className={`${ checked ? 'translate-x-6' : 'translate-x-1' } inline-block h-4 w-4 transform rounded-full bg-white transition`} /> </button> )} </Switch> <button>Submit</button> </form> ) }

当使用列表框 与 HTML 表单一起使用 或使用使用 FormData 收集其状态而不是使用 React 状态跟踪其状态的表单 API 时,这可以简化您的代码。

您提供的任何 onChange 道具都将在组件的值发生更改时被调用,以防您需要运行任何副作用,但您不需要使用它来自己跟踪组件的状态。

由于开关通常始终呈现到 DOM 中(而不是像其他组件一样被挂载/卸载),因此简单的 CSS 过渡通常足以动画化开关

import { useState } from "react"; import { Switch } from "@headlessui/react"; function MyToggle() { const [enabled, setEnabled] = useState(false); return ( <Switch checked={enabled} onChange={setEnabled}> <span /* Transition the switch's knob on state change */
className={`transform transition ease-in-out duration-200
${enabled ? "translate-x-9" : "translate-x-0"}
`}
/> {/* ... */} </Switch> ); }

由于它们是无状态的,无状态 UI 组件也与 React 生态系统中的其他动画库(如 Framer MotionReact Spring)组合良好。

默认情况下,Switch 的子元素将用作屏幕阅读器的标签。如果您使用的是 Switch.Label,您的 Switch 组件的内容将被辅助技术忽略。

单击 SwitchSwitch.Label 会打开和关闭开关。

命令描述

空格Switch 获得焦点时

切换开关

回车 当在表单中时

提交表单

所有相关的 ARIA 属性都会自动管理。

主要的 Switch 组件。

道具默认值描述
as按钮
字符串 | 组件

Switch 应该呈现为的元素或组件。

选中
布尔值

开关是否选中。

defaultChecked
T

用作非受控组件时的默认选中值。

onChange
(value: 布尔值) => void

切换开关时要调用的函数。

名称
字符串

在表单中使用此组件时使用的名称。

价值
字符串

在表单中使用此组件时,如果选中,使用的值。

渲染道具描述
选中

布尔值

开关是否选中。

道具默认值描述
as标签
字符串 | 组件

Switch.Label 应该呈现为的元素或组件。

被动错误的
布尔值

如果为 true,单击标签将不会切换 Switch

道具默认值描述
asp
字符串 | 组件

Switch.Description 应该呈现为的元素或组件。

道具默认值描述
as片段
字符串 | 组件

Switch.Group 应该呈现为的元素或组件。

如果您对使用无状态 UI 和 Tailwind CSS 的预先设计组件示例感兴趣,请查看 Tailwind UI — 我们构建的精美设计和精心制作的组件集合。

这是支持我们对像这样的开源项目工作的绝佳方式,并使我们能够改进它们并保持良好维护。