切换开关
切换开关是一种方便的界面,用于在两种状态之间切换某个值,并且提供与原生复选框元素相同的语义和键盘导航。
首先,通过 npm 安装 Headless UI
npm install @headlessui/react
开关使用 Switch
组件构建。你可以直接单击组件或在聚焦时按空格键切换开关。
切换开关会使用一个与 checked
值取反后的版本调用 onChange
函数。
import { Switch } from '@headlessui/react'
import { useState } from 'react'
function Example() {
const [enabled, setEnabled] = useState(false)
return (
<Switch
checked={enabled}
onChange={setEnabled}
className="group inline-flex h-6 w-11 items-center rounded-full bg-gray-200 transition data-[checked]:bg-blue-600"
>
<span className="size-4 translate-x-1 rounded-full bg-white transition group-data-[checked]:translate-x-6" />
</Switch>
)
}
Headless UI 会跟踪每个组件的许多状态,例如开关是否选中、弹出窗口是否打开或关闭,或菜单中哪些项当前通过键盘聚焦。
但是,由于这些组件是无头而且开箱即用完全无样式,因此在你为每个状态提供你想要的样式之前,你无法在你的 UI 中看到此信息。
为 Headless UI 组件的不同状态设置样式的最简单方法是使用每个组件公开的 data-*
属性。
例如,Switch
组件公开一个 data-checked
属性,它告诉你开关当前是否选中,以及一个 data-disabled
属性,它告诉你开关当前是否禁用。
<!-- Rendered `Switch` -->
<button data-checked data-disabled>
<!-- ... -->
</button>
使用 CSS 属性选择器根据这些数据属性的存在有条件地应用样式。如果你正在使用 Tailwind CSS,数据属性修饰符 可以简化此操作
import { Switch } from '@headlessui/react'
import { useState } from 'react'
function Example() {
const [enabled, setEnabled] = useState(false)
return (
<Switch
checked={enabled}
onChange={setEnabled}
className="group inline-flex h-6 w-11 items-center rounded-full bg-gray-200 data-[checked]:bg-blue-600 data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50" >
<span className="size-4 translate-x-1 rounded-full bg-white transition group-data-[checked]:translate-x-6" /> </Switch>
)
}
请参阅 组件 API以获取所有可用数据属性的列表。
每个组件还通过你可以用来有条件地应用不同样式或渲染不同内容的 渲染 prop 公开其当前状态的信息。
例如,Switch
组件公开一个 checked
状态,它告诉你开关当前是否选中,以及一个 disabled
状态,它告诉你开关当前是否禁用。
import { Switch } from '@headlessui/react'
import clsx from 'clsx'
import { Fragment, useState } from 'react'
function Example() {
const [enabled, setEnabled] = useState(false)
return (
<Switch checked={enabled} onChange={setEnabled} as={Fragment}>
{({ checked, disabled }) => ( <button
className={clsx(
'group inline-flex h-6 w-11 items-center rounded-full',
checked ? 'bg-blue-600' : 'bg-gray-200', disabled && 'cursor-not-allowed opacity-50' )}
>
<span className="sr-only">Enable notifications</span>
<span
className={clsx('size-4 rounded-full bg-white transition', checked ? 'translate-x-6' : 'translate-x-1')} />
</button>
)} </Switch>
)
}
请参阅 组件 API以获取所有可用渲染 prop 的列表。
使用 Field
组件包装 Label
和 Switch
,以使用生成的 ID 自动关联它们
import { Field, Label, Switch } from '@headlessui/react'
import { useState } from 'react'
function Example() {
const [enabled, setEnabled] = useState(false)
return (
<Field> <Label>Enable notifications</Label> <Switch
checked={enabled}
onChange={setEnabled}
className="group inline-flex h-6 w-11 items-center rounded-full bg-gray-200 transition data-[checked]:bg-blue-600"
>
<span className="size-4 translate-x-1 rounded-full bg-white transition group-data-[checked]:translate-x-6" />
</Switch>
</Field> )
}
默认情况下,单击 Label
会切换 Switch
,就像标签对原生 HTML 复选框那样。如果要将 Label
设置为不可单击,可以向 Label
组件添加 passive
道具
<Label passive>Enable beta features</Label>
在 Field
中使用 Description
组件,以使用 aria-describedby
属性将其与 Switch
自动关联
import { Description, Field, Label, Switch } from '@headlessui/react'
import { useState } from 'react'
function Example() {
const [enabled, setEnabled] = useState(false)
return (
<Field> <Label>Enable notifications</Label>
<Description>Get notified about important changes in your projects.</Description> <Switch
checked={enabled}
onChange={setEnabled}
className="group inline-flex h-6 w-11 items-center rounded-full bg-gray-200 transition data-[checked]:bg-blue-600"
>
<span className="size-4 translate-x-1 rounded-full bg-white transition group-data-[checked]:translate-x-6" />
</Switch>
</Field> )
}
将 disabled
道具添加到 Field
组件,以禁用 Switch
及其关联的 Label
和 Description
import { Description, Field, Label, Switch } from '@headlessui/react'
import { useState } from 'react'
function Example() {
const [enabled, setEnabled] = useState(false)
return (
<Field disabled> <Label className="data-[disabled]:opacity-50">Enable notifications</Label>
<Description className="data-[disabled]:opacity-50">
Get notified about important changes in your projects.
</Description>
<Switch
checked={enabled}
onChange={setEnabled}
className="group inline-flex h-6 w-11 items-center rounded-full bg-gray-200 transition data-[checked]:bg-blue-600 data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50"
>
<span className="size-4 translate-x-1 rounded-full bg-white transition group-data-[checked]:translate-x-6" />
</Switch>
</Field>
)
}
你还可以通过直接将 disabled 道具添加到 Switch
本身,在 Field
外部禁用开关。
如果你向 Switch
添加 name
道具,系统将呈现一个隐藏的 input
元素并使其与开关状态保持同步。
import { Switch } from '@headlessui/react'
import { useState } from 'react'
function Example() {
const [enabled, setEnabled] = useState(false)
return (
<form action="/accounts" method="post">
<Switch
checked={enabled}
onChange={setEnabled}
name="terms-of-service" className="group inline-flex h-6 w-11 items-center rounded-full bg-gray-200 transition data-[checked]:bg-blue-600"
>
<span className="size-4 translate-x-1 rounded-full bg-white transition group-data-[checked]:translate-x-6" />
</Switch>
<button>Submit</button>
</form>
)
}
这让你可以在原生 HTML <form>
中使用开关,就像你的开关是原生 HTML 表单控件一样进行传统表单提交。
默认情况下,当开关处于选中状态时,该值将为 on
,而当开关处于未选中状态时,则该值不存在。
<!-- Rendered hidden input -->
<input type="hidden" name="terms-of-service" value="on" />
如果需要,可以使用 value
道具自定义该值
import { Switch } from '@headlessui/react'
import { useState } from 'react'
function Example() {
const [enabled, setEnabled] = useState(false)
return (
<form action="/accounts" method="post">
<Switch
checked={enabled}
onChange={setEnabled}
name="terms-of-service"
value="accept" className="group inline-flex h-6 w-11 items-center rounded-full bg-gray-200 transition data-[checked]:bg-blue-600"
>
<span className="size-4 translate-x-1 rounded-full bg-white transition group-data-[checked]:translate-x-6" />
</Switch>
<button>Submit</button>
</form>
)
}
当开关处于选中状态时,隐藏输入会使用你的自定义值
<!-- Rendered hidden input -->
<input type="hidden" name="terms-of-service" value="accept" />
字符串之类的基本值将呈现为包含该值的单个隐藏输入,但对象之类的复杂值将使用方括号符号对名称进行编码,并编码成多个输入。
如果您省略 checked
属性,Headless UI 将为您在内部跟踪其状态,允许您将其用作 不受控制的组件。
处于不受控状态时,您可以使用 defaultChecked
属性来默认选中 Switch
。
import { Switch } from '@headlessui/react'
function Example() {
return (
<form action="/accounts" method="post">
<Switch
defaultChecked={true} name="terms-of-service"
className="group inline-flex h-6 w-11 items-center rounded-full bg-gray-200 transition data-[checked]:bg-blue-600"
>
<span className="size-4 translate-x-1 rounded-full bg-white transition group-data-[checked]:translate-x-6" />
</Switch>
<button>Submit</button>
</form>
)
}
当使用带有 HTML 表单 或使用通过 FormData 收集其状态(而不是使用 React 状态对其进行跟踪)的表单 API 时,这可以简化您的代码。
如果您需要运行任何副作用,您提供的任何 onChange
属性仍将在组件的值发生变化时被调用,但您无需使用它来自己跟踪组件的状态。
由于开关通常始终渲染到 DOM(而不是像其他组件那样挂载/卸载),因此简单的 CSS 过渡通常足以让您的开关动起来。
import { Switch } from '@headlessui/react'
import { useState } from 'react'
function Example() {
const [enabled, setEnabled] = useState(false)
return (
<Switch
checked={enabled}
onChange={setEnabled}
className="group inline-flex h-6 w-11 items-center rounded-full bg-gray-200 transition data-[checked]:bg-blue-600" >
<span className="size-4 translate-x-1 rounded-full bg-white transition group-data-[checked]:translate-x-6" /> </Switch>
)
}
因为它们无渲染,所以 Headless UI 组件还可以很好地与 React 生态系统中的其他动画库组合使用,例如 Framer Motion 和 React Spring。
Switch
组件默认渲染一个 button
。
使用 as
属性将组件作为不同的元素或作为您自己的自定义组件进行渲染,确保您的自定义组件 转发 ref,以便 Headless UI 可以正确地连接各部分。
import { Switch } from '@headlessui/react'
import { useState } from 'react'
function Example() {
const [enabled, setEnabled] = useState(false)
return (
<Switch
as="div" checked={enabled}
onChange={setEnabled}
className="group inline-flex h-6 w-11 items-center rounded-full bg-gray-200 transition data-[checked]:bg-blue-600"
>
<span className="size-4 translate-x-1 rounded-full bg-white transition group-data-[checked]:translate-x-6" />
</Switch>
)
}
命令 | 说明 |
空格键当 | 切换开关 |
回车键在表单中时 | 提交表单 |
属性 | 默认值 | 说明 |
as | button | 字符串 | 组件 开关应作为该元素渲染。 |
checked | — | 布尔值 开关是否处于选中状态。 |
defaultChecked | — | T 用作不受控组件时的默认选中值。 |
onChange | — | (value: Boolean) => void 开关切换时调用的函数。 |
name | — | 字符串 使用元素时使用的名称 |
表单中的内容。 | — | 字符串 form元素的 id 所属的表单。元素如果提供了 |
会将其状态添加到最近的父级 | — | 字符串 value |
在表单中使用此组件时使用的值(如果选中)。 | 数据属性 | 说明 |
Render 属性checked | checked |
data-元素是否为 |
Render 属性选中状态。 | 选中状态。 |
data-元素disabled |
Render 属性处于禁用状态。 | 处于禁用状态。 |
data-元素focus |
Render 属性处于聚焦状态。 | 处于聚焦状态。 |
data-元素hover |
Render 属性处于悬停状态。 | 处于悬停状态。 |
data-元素active |
Render 属性处于活动或按下的状态。 | 处于活动或按下的状态。 |
autofocus |
Render 属性 |
|
changing 选中的状态是否正在更改。 |
当checked
状态发生变化时,changing
将在两个动画帧内变为true
,这使你可以微调过渡。
属性 | 默认值 | 说明 |
as | 将 | 字符串 | 组件 开关应作为该div渲染。 |
选中状态。 | field | 布尔值 false |
在表单中使用此组件时使用的值(如果选中)。 | 数据属性 | 说明 |
Render 属性选中状态。 | 选中状态。 |
false |
字段是否禁用。
属性 | 默认值 | 说明 |
as |
| 字符串 | 组件 开关应作为该 |
label | field | 布尔值 passive |
在表单中使用此组件时使用的值(如果选中)。 | 数据属性 | 说明 |
Render 属性选中状态。 | 选中状态。 |
如果为 true,单击标签不会聚焦关联的表单控件。 |
父级Field
是否禁用。
属性 | 默认值 | 说明 |
as |
| 字符串 | 组件 开关应作为该p渲染。 |
在表单中使用此组件时使用的值(如果选中)。 | 数据属性 | 说明 |
Render 属性选中状态。 | 选中状态。 |
如果为 true,单击标签不会聚焦关联的表单控件。 |
description
如果你对使用 Headless UI 预先设计好的Tailwind CSS 切换和开关示例感兴趣
,那就来瞧瞧Tailwind UI吧,它是由我们构建的一系列设计精美、制作精良的组件。