Switch(开关)用于改动后立刻生效的设置,例如飞行模式或通知偏好。两种状态下标签文字保持不变:标签说明这是什么设置,开关本身表示它开没开。需要勾选后随表单一起提交的选项用复选框;按下状态属于工具栏按钮时用切换按钮。
演示
点击开关,或聚焦后按空格,下方的状态文字随之更新:
状态:开
组合
Switch
└── SwitchThumb把开关和它的文字一起包在 label 里。SwitchThumb 始终挂载并继承开关的状态,所以可以用 CSS 把它从一端滑到另一端。
用法
import { Switch, SwitchThumb } from "@ui-sdk/react/switch";
<label>
飞行模式
<Switch><SwitchThumb /></Switch>
</label>;点击文字或开关都能切换。Switch 渲染的是带 switch 角色的按钮,另有一个隐藏的原生复选框参与表单。
标签、说明与整卡点击
设置需要解释时,用字段的 FieldLabel 与 FieldDescription,字段会把两者都关联到开关:
import { Field, FieldDescription, FieldLabel } from "@ui-sdk/react/field";
<Field>
<div>
<FieldLabel>每周汇总</FieldLabel>
<FieldDescription>每周一发送上周摘要。</FieldDescription>
</div>
<Switch defaultChecked><SwitchThumb /></Switch>
</Field>;想让整张卡片都可点击,把标签的 ::after 铺满卡片,再用 :has([data-checked]) 表达卡片的开启态:
[data-slot="field"] { position: relative; }
[data-slot="field"] [data-slot="field-label"]::after {
content: "";
position: absolute;
inset: 0;
}
[data-slot="field"]:has([data-checked]) { border-color: var(--color-brand); }受控与保存后再生效
传 checked 与 onCheckedChange 即可自己持有状态。details.reason 区分 press、reset、restore 与 imperative,details.cancel() 保留当前状态。
要让开关在保存成功后才真正切换,先在回调里取消这次变更,保存完成后用控制器以 imperative 原因写回,并在回调里放行这个原因:
import { createSwitchController, Switch, SwitchThumb } from "@ui-sdk/react/switch";
const backups = createSwitchController();
const [saving, setSaving] = React.useState(false);
<label>
自动备份
<Switch
controller={backups}
aria-busy={saving}
onCheckedChange={(next, details) => {
if (details.reason === "imperative") return;
details.cancel();
if (saving) return;
setSaving(true);
save(next).then(() => {
backups.setChecked(next, { reason: "imperative" });
setSaving(false);
});
}}
>
<SwitchThumb />
</Switch>
</label>;aria-busy 告知辅助技术正在处理,真正阻止重复改动的是回调里的取消与 saving 判断。保存期间可以在 SwitchThumb 里放一个加载指示。控制器由 createSwitchController() 创建,提供 toggle / getChecked / setChecked / focus,写入被取消时返回 false。参见控制器与状态与事件。
表单与校验
传 name 才参与提交:开启时提交 value(默认 "on"),关闭时不提交任何数据;重置回到 defaultChecked,form 可关联页面中其他表单的 id。
<FieldSet>
<FieldLegend>邮件通知</FieldLegend>
<label>
产品动态
<Switch name="updates"><SwitchThumb /></Switch>
</label>
<label>
每周汇总
<Switch name="digest" value="weekly" defaultChecked><SwitchThumb /></Switch>
</label>
</FieldSet>required 会阻止提交直到开关打开。放进带 FieldError 的字段里,错误显示在开关下方并与它关联。disabled 不可聚焦、不可改动、不提交,外层 <fieldset disabled> 同样生效;readOnly 保留聚焦与提交,但不接受改动。参见表单。
样式与状态
开关与滑块都用 data-checked / data-unchecked 表达开关态,另有 data-disabled、data-readonly、data-required。校验失败时只有 switch 会得到 data-invalid 与 aria-invalid,switch-thumb 不会。
[data-slot="switch"] {
inline-size: 2.5rem;
block-size: 1.5rem;
padding: 2px;
border-radius: 999px;
background: var(--color-border);
}
[data-slot="switch"][data-checked] { background: var(--color-brand); }
[data-slot="switch-thumb"] {
block-size: 100%;
aspect-ratio: 1;
border-radius: 50%;
background: var(--color-card);
transition: translate 0.15s ease;
}
[data-slot="switch"][data-checked] [data-slot="switch-thumb"] { translate: 1rem 0; }
[dir="rtl"] [data-slot="switch"][data-checked] [data-slot="switch-thumb"] { translate: -1rem 0; }用 translate 滑动滑块,并在 dir="rtl" 下反向。Switch 与 SwitchThumb 都接受 render:传元素时它的 props 与部件 props 合并,传函数时收到 props 与状态,可以按 state.checked 渲染不同图标,但要保留部件的 props,语义与键盘行为才不会断。详见样式与动画与部件与组合。
API
Switch 常用属性:
| 属性 | 类型 | 说明 |
|---|---|---|
checked |
boolean |
受控开关状态。 |
defaultChecked |
boolean |
非受控初始状态,重置回到它。 |
onCheckedChange |
(checked, details) => void |
状态将要变化时触发,可 details.cancel()。 |
name / form |
string |
提交字段名(不传则不提交)与关联表单 id。 |
value |
string |
开启时提交的值,默认 "on"。 |
required |
boolean |
未打开时阻止提交。 |
disabled / readOnly |
boolean |
禁用、只读。 |
开关与滑块共享同一套状态:checked、disabled、readOnly、required,可用于渲染函数与按状态取类名。
键盘与无障碍
| 按键 | 行为 |
|---|---|
Tab |
移动到下一个开关。 |
Space |
打开或关闭。 |
Enter |
提交所在表单,绝不切换开关。 |
遵循 WAI-ARIA switch 模式。标签在两种状态下保持同一句话,状态由 switch 角色自身表达,不要把「开启通知」「关闭通知」写进名字。用包裹的 label、FieldLabel 或 aria-label 命名。