Switch 开关
二态开关 —— 3 种尺寸、loading 与 disabled 态,原生 input role=switch 语义保留。
基础用法
底层是 <CfInput type="checkbox" role="switch">,键盘 / 表单 / 屏幕阅读器全部直接可用。
背景
<script setup lang="ts">
import { ref } from 'vue';
import { CfSwitch } from '@chufix-design/vue';
const wifi = ref(true);
</script>
<template>
<CfSwitch v-model="wifi">Wi-Fi {{ wifi ? '已开启' : '已关闭' }}</CfSwitch>
</template> <script setup>
import { ref } from 'vue';
import { CfSwitch } from '@chufix-design/vue';
const wifi = ref(true);
</script>
<template>
<CfSwitch v-model="wifi">Wi-Fi {{ wifi ? '已开启' : '已关闭' }}</CfSwitch>
</template> import { useState } from 'react';
import { CfSwitch } from '@chufix-design/react';
export default function Demo() {
const [wifi, setWifi] = useState(true);
return (
<CfSwitch checked={wifi} onChange={(e) => setWifi(e.target.checked)}>
Wi-Fi {wifi ? '已开启' : '已关闭'}
</CfSwitch>
);
} import { useState } from 'react';
import { CfSwitch } from '@chufix-design/react';
export default function Demo() {
const [wifi, setWifi] = useState(true);
return (
<CfSwitch checked={wifi} onChange={(e) => setWifi(e.target.checked)}>
Wi-Fi {wifi ? '已开启' : '已关闭'}
</CfSwitch>
);
} 尺寸
背景
<script setup lang="ts">
import { ref } from 'vue';
import { CfSwitch } from '@chufix-design/vue';
const a = ref(true);
const b = ref(false);
const c = ref(false);
</script>
<template>
<div class="demo-stack">
<CfSwitch v-model="a" size="sm">Small</CfSwitch>
<CfSwitch v-model="b" size="md">Medium(默认)</CfSwitch>
<CfSwitch v-model="c" size="lg">Large</CfSwitch>
</div>
</template> <script setup>
import { ref } from 'vue';
import { CfSwitch } from '@chufix-design/vue';
const a = ref(true);
const b = ref(false);
const c = ref(false);
</script>
<template>
<div class="demo-stack">
<CfSwitch v-model="a" size="sm">Small</CfSwitch>
<CfSwitch v-model="b" size="md">Medium(默认)</CfSwitch>
<CfSwitch v-model="c" size="lg">Large</CfSwitch>
</div>
</template> <CfSwitch size="sm">Small</CfSwitch>
<CfSwitch size="md">Medium</CfSwitch>
<CfSwitch size="lg">Large</CfSwitch> <CfSwitch size="sm">Small</CfSwitch>
<CfSwitch size="md">Medium</CfSwitch>
<CfSwitch size="lg">Large</CfSwitch> 状态
loading 在 thumb 内显示 spinner 并暂时阻止切换;disabled 禁用整个开关。
背景
<script setup lang="ts">
import { ref } from 'vue';
import { CfSwitch } from '@chufix-design/vue';
const sync = ref(false);
const loading = ref(false);
async function toggle(v: boolean) {
loading.value = true;
await new Promise((r) => setTimeout(r, 1000));
sync.value = v;
loading.value = false;
}
</script>
<template>
<div class="demo-stack">
<CfSwitch
:model-value="sync"
:loading="loading"
@change="toggle"
>自动同步(点击触发 1s 异步)</CfSwitch>
<CfSwitch :model-value="true" disabled>禁用 · 已开</CfSwitch>
<CfSwitch :model-value="false" disabled>禁用 · 已关</CfSwitch>
</div>
</template> <script setup>
import { ref } from 'vue';
import { CfSwitch } from '@chufix-design/vue';
const sync = ref(false);
const loading = ref(false);
async function toggle(v) {
loading.value = true;
await new Promise((r) => setTimeout(r, 1000));
sync.value = v;
loading.value = false;
}
</script>
<template>
<div class="demo-stack">
<CfSwitch
:model-value="sync"
:loading="loading"
@change="toggle"
>自动同步(点击触发 1s 异步)</CfSwitch>
<CfSwitch :model-value="true" disabled>禁用 · 已开</CfSwitch>
<CfSwitch :model-value="false" disabled>禁用 · 已关</CfSwitch>
</div>
</template> import { useState } from 'react';
import { CfSwitch } from '@chufix-design/react';
export default function Demo() {
const [sync, setSync] = useState(false);
const [loading, setLoading] = useState(false);
async function toggle(e) {
setLoading(true);
await new Promise((r) => setTimeout(r, 1000));
setSync(e.target.checked);
setLoading(false);
}
return (
<>
<CfSwitch checked={sync} loading={loading} onChange={toggle}>
自动同步(点击触发 1s 异步)
</CfSwitch>
<CfSwitch checked disabled>禁用 · 已开</CfSwitch>
</>
);
} import { useState } from 'react';
import { CfSwitch } from '@chufix-design/react';
export default function Demo() {
const [sync, setSync] = useState(false);
const [loading, setLoading] = useState(false);
async function toggle(e) {
setLoading(true);
await new Promise((r) => setTimeout(r, 1000));
setSync(e.target.checked);
setLoading(false);
}
return (
<>
<CfSwitch checked={sync} loading={loading} onChange={toggle}>
自动同步(点击触发 1s 异步)
</CfSwitch>
<CfSwitch checked disabled>禁用 · 已开</CfSwitch>
</>
);
} 事件与异步
loading 会暂时阻止再次切换,适合保存设置、权限开关、远程同步等异步场景。change 会携带 checked 与 name,React 额外提供 onCheckedChange 便于直接拿布尔值。
背景
idle
点击开关后会模拟异步提交,并显示事件 meta。<script setup lang="ts">
import { ref } from 'vue';
import { CfBadge, CfSwitch, type SwitchChangeMeta } from '@chufix-design/vue';
const enabled = ref(false);
const loading = ref(false);
const phase = ref('idle');
const logs = ref(['点击开关后会模拟异步提交,并显示事件 meta。']);
function push(entry: string) {
logs.value = [entry, ...logs.value].slice(0, 5);
}
async function onChange(next: boolean, meta: SwitchChangeMeta) {
loading.value = true;
phase.value = 'saving';
push(`change: checked=${next} / name=${meta.name ?? '-'}`);
await new Promise((resolve) => setTimeout(resolve, 450));
enabled.value = next;
loading.value = false;
phase.value = next ? 'enabled' : 'disabled';
push(`saved: ${next ? 'enabled' : 'disabled'}`);
}
</script>
<template>
<div class="switch-events">
<CfSwitch
:model-value="enabled"
:loading="loading"
name="auto-sync"
@change="onChange"
@focus="push('focus: trigger focused')"
@blur="push('blur: trigger blurred')"
>
自动同步
</CfSwitch>
<div class="switch-events__status">
<CfBadge tone="info" :content="phase" />
<div class="switch-events__log" aria-live="polite">
<code v-for="entry in logs" :key="entry">{{ entry }}</code>
</div>
</div>
</div>
</template>
<style scoped>
.switch-events {
display: grid;
gap: 12px;
width: min(100%, 460px);
}
.switch-events__status {
display: flex;
align-items: flex-start;
gap: 10px;
}
.switch-events__log {
display: grid;
gap: 4px;
min-width: 0;
}
.switch-events__log code {
white-space: normal;
}
</style> <script setup>
import { ref } from 'vue';
import { CfBadge, CfSwitch } from '@chufix-design/vue';
const enabled = ref(false);
const loading = ref(false);
const phase = ref('idle');
const logs = ref(['点击开关后会模拟异步提交,并显示事件 meta。']);
function push(entry) {
logs.value = [entry, ...logs.value].slice(0, 5);
}
async function onChange(next, meta) {
loading.value = true;
phase.value = 'saving';
push(`change: checked=${next} / name=${meta.name ?? '-'}`);
await new Promise((resolve) => setTimeout(resolve, 450));
enabled.value = next;
loading.value = false;
phase.value = next ? 'enabled' : 'disabled';
push(`saved: ${next ? 'enabled' : 'disabled'}`);
}
</script>
<template>
<div class="switch-events">
<CfSwitch
:model-value="enabled"
:loading="loading"
name="auto-sync"
@change="onChange"
@focus="push('focus: trigger focused')"
@blur="push('blur: trigger blurred')"
>
自动同步
</CfSwitch>
<div class="switch-events__status">
<CfBadge tone="info" :content="phase" />
<div class="switch-events__log" aria-live="polite">
<code v-for="entry in logs" :key="entry">{{ entry }}</code>
</div>
</div>
</div>
</template>
<style scoped>
.switch-events {
display: grid;
gap: 12px;
width: min(100%, 460px);
}
.switch-events__status {
display: flex;
align-items: flex-start;
gap: 10px;
}
.switch-events__log {
display: grid;
gap: 4px;
min-width: 0;
}
.switch-events__log code {
white-space: normal;
}
</style> <CfSwitch
checked={enabled}
loading={loading}
name="auto-sync"
onCheckedChange={async (checked, meta) => {
setLoading(true);
await save(checked, meta.name);
setEnabled(checked);
setLoading(false);
}}
>
自动同步
</CfSwitch> <CfSwitch
checked={enabled}
loading={loading}
name="auto-sync"
onCheckedChange={async (checked, meta) => {
setLoading(true);
await save(checked, meta.name);
setEnabled(checked);
setLoading(false);
}}
>
自动同步
</CfSwitch> API
| Prop | 类型 | 默认值 | 说明 |
|---|---|---|---|
size | 'sm' | 'md' | 'lg' | 'md' | 整体尺寸 |
disabled | boolean | false | 禁用 |
loading | boolean | false | 显示 spinner,并暂时阻止切换 |
name | string | — | 原生 input name |
id | string | — | 原生 input id |
Events
| Vue 事件 | React 回调 | payload | 说明 |
|---|---|---|---|
change | onCheckedChange | (checked, { event, checked, name }) | 切换状态变化 |
focus / blur | onFocus / onBlur | FocusEvent | 原生焦点事件 |
双向绑定
- Vue:
v-model绑定到boolean;@change拿到boolean值。 - React:受控传
checked+onChange;非受控传defaultChecked。onChange是原生 ChangeEvent,onCheckedChange直接返回布尔值。
反馈与讨论
Switch 开关 的讨论