Toggle(切换按钮)渲染一个原生按钮,用 aria-pressed 报告按下状态:按一下保持按下,再按一下弹起,适合收藏文档、开启加粗这类操作。把多个按钮放进 ToggleGroup 可以共享一个值、用方向键在按钮间移动焦点,例如编辑器的格式栏或视图切换器。需要「方向键移动即选中」的单选时用单选框。
演示
点击切换按钮,或聚焦后按空格、回车,下方的状态文字随之更新:
状态:未收藏
组合
部件都从 @ui-sdk/react/toggle 导入。单独使用时就是 Toggle 加可选的 ToggleIcon;分组时:
ToggleGroup
├── Toggle
│ └── ToggleIcon
└── Toggle
└── ToggleIconToggleIcon 可选,承载图标并对屏幕阅读器隐藏。组内每个 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 |
聚焦第一个 / 最后一个按钮 |