切换开关

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

首先,通过 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 组件包装 LabelSwitch,以使用生成的 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 及其关联的 LabelDescription

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 MotionReact 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> ) }

命令说明

空格键Switch 聚焦时

切换开关

回车键在表单中时

提交表单

主开关组件。

属性默认值说明
asbutton
字符串 | 组件

开关应作为该元素渲染。

checked
布尔值

开关是否处于选中状态。

defaultChecked
T

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

onChange
(value: Boolean) => void

开关切换时调用的函数。

name
字符串

使用元素时使用的名称

表单中的内容。
字符串

form元素的 id

所属的表单。元素如果提供了name但没有form

会将其状态添加到最近的父级form元素。
字符串

value

在表单中使用此组件时使用的值(如果选中)。数据属性说明
Render 属性checkedchecked

布尔值

data-元素是否为

Render 属性选中状态。选中状态。

布尔值

data-元素disabled

Render 属性处于禁用状态。处于禁用状态。

布尔值

data-元素focus

Render 属性处于聚焦状态。处于聚焦状态。

布尔值

data-元素hover

Render 属性处于悬停状态。处于悬停状态。

布尔值

data-元素active

Render 属性处于活动或按下的状态。处于活动或按下的状态。

布尔值

autofocus

Render 属性autoFocus属性是否已设置为trueautoFocus属性是否已设置为true

布尔值

changing

选中的状态是否正在更改。

checked状态发生变化时,changing将在两个动画帧内变为true,这使你可以微调过渡。

属性默认值说明
asLabelDescription和表单控件组合在一起。
字符串 | 组件

开关应作为该div渲染。

选中状态。field
布尔值

false

在表单中使用此组件时使用的值(如果选中)。数据属性说明
Render 属性选中状态。选中状态。

布尔值

false

字段是否禁用。

属性默认值说明
asLabel组件标记表单控件。
字符串 | 组件

开关应作为该Label组件标记表单控件。渲染。

labelfield
布尔值

passive

在表单中使用此组件时使用的值(如果选中)。数据属性说明
Render 属性选中状态。选中状态。

布尔值

如果为 true,单击标签不会聚焦关联的表单控件。

父级Field是否禁用。

属性默认值说明
asDescription组件描述表单控件。
字符串 | 组件

开关应作为该p渲染。

在表单中使用此组件时使用的值(如果选中)。数据属性说明
Render 属性选中状态。选中状态。

布尔值

如果为 true,单击标签不会聚焦关联的表单控件。

description

如果你对使用 Headless UI 预先设计好的Tailwind CSS 切换和开关示例感兴趣

,那就来瞧瞧Tailwind UI吧,它是由我们构建的一系列设计精美、制作精良的组件。