switch · minimal · 灵感来源:Refactoring UI(开启 = 实心前景轨,关闭 = 描边空槽)
Wi-Fi
已连接 · UI-Echo-5G
蓝牙
已关闭
飞行模式
演示禁用态
小号尺寸
点击 / 空格切换 · 文案随状态联动
Flat Minimal Switch · 极简开关 · 开关 / Minimal
做一个 Flat Minimal Switch(极简开关):
- role=switch + aria-checked,Space / Enter 切换
- 关闭 = 描边空槽,开启 = 实心前景轨 + 反色圆钮
- 圆钮平移与轨道变色同步(200ms)
- 轨道高度 ≥24px,按压有 0.97 缩放反馈
用这些 CSS 变量:--color-divider --color-surface --color-text --color-text-muted --dur-fast --ease-standard --shadow-sm
灵感参考:Refactoring UI(开启 = 实心前景轨,关闭 = 描边空槽)--color-divider --color-surface --color-text --color-text-muted --dur-fast --ease-standard --shadow-sm
焦点 / 按压 / 禁用
(尚未接进整页场景)
/r/switch--minimal.json
免外壳的 markdown 版:/llms/components/switch--minimal.md--shadow-sm,平移仍是 --dur-fast / --ease-standard.press-feedback 0.97对比玻璃开关:没有 accent 填充与内高光;暗色下前景色反成浅色,对比更硬。
import { useState } from 'react';
interface Props {
checked?: boolean;
defaultChecked?: boolean;
onChange?: (checked: boolean) => void;
disabled?: boolean;
size?: 'sm' | 'md';
'aria-label'?: string;
}
// 轨道高度都 ≥24px:SC 2.5.8 要求指针目标至少 24×24 CSS 像素
const dims = {
sm: { track: 'w-8 h-6', thumb: 'size-4', on: 'translateX(12px)' },
md: { track: 'w-11 h-[26px]', thumb: 'size-[22px]', on: 'translateX(18px)' },
};
/**
* 极简开关:与玻璃开关同一份 props,
* 关闭 = 描边空槽,开启 = 实心墨轨反白圆钮;没有模糊与内高光。
*/
export function MinimalSwitch({
checked,
defaultChecked,
onChange,
disabled,
size = 'md',
...rest
}: Props) {
const isControlled = checked !== undefined;
const [internal, setInternal] = useState(defaultChecked ?? false);
const on = isControlled ? checked : internal;
const d = dims[size];
const toggle = () => {
const next = !on;
if (!isControlled) setInternal(next);
onChange?.(next);
};
return (
<button
type="button"
role="switch"
aria-checked={on}
disabled={disabled}
onClick={toggle}
className={`
press-feedback
relative inline-flex items-center shrink-0
${d.track} rounded-full p-[2px]
border
transition-[background-color,border-color] duration-[var(--dur-fast)] ease-[var(--ease-standard)]
cursor-pointer
${on
? 'bg-[var(--color-text)] border-transparent'
: 'bg-transparent border-[var(--color-divider)]'}
disabled:opacity-40 disabled:cursor-not-allowed
`}
{...rest}
>
<span
aria-hidden="true"
className={`
${d.thumb} rounded-full
bg-[var(--color-surface)] border border-[var(--color-divider)]
shadow-[var(--shadow-sm)]
transition-transform duration-[var(--dur-fast)] ease-[var(--ease-standard)]
`}
style={{ transform: on ? d.on : 'translateX(0)' }}
/>
</button>
);
}
src/components/switch/minimal/