跳到主要内容

切换按钮

Toggle 是带按下状态的按钮:单独用于收藏、置顶,或组成共享值、方向键导航的切换按钮组。

Toggle(切换按钮)渲染一个原生按钮,用 aria-pressed 报告按下状态:按一下保持按下,再按一下弹起,适合收藏文档、开启加粗这类操作。把多个按钮放进 ToggleGroup 可以共享一个值、用方向键在按钮间移动焦点,例如编辑器的格式栏或视图切换器。需要「方向键移动即选中」的单选时用单选框。

演示

点击切换按钮,或聚焦后按空格、回车,下方的状态文字随之更新:

状态:未收藏

组合

部件都从 @ui-sdk/react/toggle 导入。单独使用时就是 Toggle 加可选的 ToggleIcon;分组时:

ToggleGroup
├── Toggle
│   └── ToggleIcon
└── Toggle
    └── ToggleIcon

ToggleIcon 可选,承载图标并对屏幕阅读器隐藏。组内每个 Toggle 都要给 value,组的值就是被按下按钮的 value 组成的数组。

用法

import { Toggle, ToggleIcon } from "@ui-sdk/react/toggle";

function Example() {
  return (
    <Toggle defaultPressed>
      <ToggleIcon>★</ToggleIcon>
      收藏
    </Toggle>
  );
}

两种状态下文字保持一致:屏幕阅读器会读出按钮是否按下,不必把标签改成「已收藏」。只有图标没有文字时必须给 aria-label。按钮始终是 type="button",在表单里按下它不会触发提交。

分组与单选

multiple 允许多个按钮同时按下,方向键只移动焦点、不改变按下状态:

import { Toggle, ToggleGroup } from "@ui-sdk/react/toggle";

<ToggleGroup multiple defaultValue={["bold"]} aria-label="文本格式">
  <Toggle value="bold">加粗</Toggle>
  <Toggle value="italic">斜体</Toggle>
  <Toggle value="underline">下划线</Toggle>
</ToggleGroup>;

不传 multiple 时同一时刻只有一个按下:按新按钮会弹起其他按钮,再按当前按下的按钮会清空选择。设 deselectable={false} 让选择始终保留一项,并搭配 defaultValue 指定初始项,例如视图切换器用 defaultValue={["list"]} 保证总有一种视图被选中。

组的值始终是数组,按按钮在文档里的顺序排列;组内按钮自己的 pressed / defaultPressed 不生效。有方向含义的图标(箭头、对齐)给 ToggleIcon 加 mirror,在从右到左的文本里水平翻转;时钟、对勾这类描绘事物的图标不用翻。

禁用与可聚焦

disabled 可以设在按钮或整个组上,禁用的按钮仍能以按下状态开始(<Toggle disabled defaultPressed>),外层禁用的 <fieldset> 也会禁用里面的按钮。

单独的禁用按钮会离开 Tab 顺序;组或工具栏里的禁用按钮仍能被方向键到达,报告 aria-disabled 但忽略按下。给单独的按钮加 focusableWhenDisabled 可以保持可聚焦,并用 aria-describedby 解释原因:

<Toggle disabled focusableWhenDisabled aria-describedby="pin-hint">
  置顶
</Toggle>
<p id="pin-hint">只有所有者可以置顶消息。</p>

受控与控制器

单独的按钮用 pressed + onPressedChange,回调里调用 details.cancel() 保持现状。details.reason 为 "press"(用户按下)或 "imperative"(控制器调用):

"use client";

import * as React from "react";
import { createToggleController, Toggle } from "@ui-sdk/react/toggle";

function StarButton() {
  const [starred, setStarred] = React.useState(false);
  const [star] = React.useState(createToggleController);
  return (
    <>
      <Toggle pressed={starred} onPressedChange={setStarred} controller={star}>
        收藏
      </Toggle>
      <button type="button" onClick={() => star.toggle()}>快捷键收藏</button>
    </>
  );
}

每个挂载的按钮配一个 createToggleController():isPressed / setPressed / toggle / focus。写方法接受可选的 { reason, event },像用户按下一样触发回调,被取消时返回 false。

组用 value + onValueChange(数组),挂载时不触发。按钮自己的 onPressedChange 先于组的回调运行,在任一处 details.cancel() 都会取消这次变更。createToggleGroupController() 提供 getValue / setValue / isPressed(value) / toggle(value) / focus(value?),setValue([]) 清空选择;非 multiple 时数组里只有第一个值生效。参见状态与事件与控制器。

样式与状态

  • 按钮 toggle:data-pressed(按下)、data-disabled、data-orientation(组的方向)、data-has-icon(含图标部件)。
  • 图标 toggle-icon:data-mirror(设了 mirror)。
  • 组 toggle-group:data-orientation(horizontal / vertical)、data-disabled、data-has-icons(任一按钮含图标,方便没有图标的按钮对齐)。
[data-slot="toggle"] {
  padding: 0.375rem 0.75rem;
  border: 1px solid var(--color-border);
  border-radius: 8px;
  background: var(--color-card);
}
[data-slot="toggle"][data-pressed] {
  border-color: var(--color-brand);
  background: var(--color-brand-wash);
  color: var(--color-brand);
}
[data-slot="toggle"][data-disabled] {
  opacity: 0.5;
}
[data-slot="toggle-group"] {
  display: inline-flex;
  gap: 0.25rem;
}
[data-slot="toggle-group"][data-orientation="vertical"] {
  flex-direction: column;
}

orientation="vertical" 让组改用上下键移动;loop={false} 让方向键停在两端而不循环。每个部件都接受 render,可以按 state.pressed 渲染实心或空心图标,但要把部件的 props 留在按钮上,aria-pressed 与键盘行为才不会断。详见样式与动画与部件与组合。

API

Toggle 常用属性:

属性 类型 说明
pressed / defaultPressed boolean 受控 / 初始按下状态,组内由组的值决定
onPressedChange (pressed, details) => void 按下回调,details.cancel() 可取消
value string 组内标识,默认为生成的 id
disabled boolean 禁止按下
focusableWhenDisabled boolean 禁用时保持可聚焦,改用 aria-disabled
controller object createToggleController() 创建

ToggleGroup 常用属性:

属性 类型 说明
value / defaultValue string[] 受控 / 初始按下值,始终是数组
onValueChange (value, details) => void 值变更回调,挂载时不触发
multiple boolean 允许多个同时按下
deselectable boolean 单选时能否再按取消,multiple 下忽略
orientation "horizontal" | "vertical" 方向键的移动方向
loop boolean 方向键是否首尾循环,默认循环
disabled boolean 禁用整组

ToggleIcon 只有 mirror(boolean)。切换按钮不提交表单值:表单里的选项用复选框或单选框;确要把组留在表单里时,自行把值复制进隐藏输入。

键盘与无障碍

按钮遵循 WAI-ARIA 的切换按钮模式:原生 button 加 aria-pressed。组借用工具栏的键盘模型,但角色是 group 而不是 toolbar,因此可以嵌进工具栏;也不是 radiogroup,因为成员是按钮。组只有一个 Tab 停靠点:首次 Tab 进入第一个启用的按下按钮(没有则第一个启用按钮),之后回到上次聚焦的按钮。

按键 行为
Enter / Space 按下或弹起聚焦的按钮
Tab 进入或离开组
ArrowLeft / ArrowRight 水平组内移动焦点,RTL 下反向,不改变按下状态
ArrowUp / ArrowDown 垂直组内移动焦点
Home / End 聚焦第一个 / 最后一个按钮

相关组件

  • 开关:改动立即生效的设置,角色是 switch,随表单提交值。
  • 单选框:方向键移动焦点的同时即选中。
  • 复选框:需要随表单提交的勾选项。

输入关键词搜索…

↑↓ 移动↵ 选择Esc 关闭