shadcn-vue Switch 组件完全指南:安装、用法、表单集成与源码解析
【免费下载链接】shadcn-vueVue port of shadcn-ui项目地址: https://gitcode.com/gh_mirrors/sh/shadcn-vue
导读
Switch(开关)是表单与设置面板中最常用的切换控件,允许用户在「已选中」与「未选中」两种状态之间切换。本指南基于 shadcn-vue 仓库中的官方文档 deprecated/www/src/content/docs/components/switch.md 展开,结合仓库内 Switch 组件的真实源码(apps/v4/registry/bases/reka/ui/switch/Switch.vue)与各风格示例,系统讲解如何安装、导入、使用 Switch,如何在滑块(thumb)内嵌入图标,以及如何将 Switch 集成进 VeeValidate + Zod 的表单体系。读完本文,你将掌握 Switch 组件的全部常见用法,并能根据需求自行扩展尺寸、样式与无障碍行为。
组件概览
Switch 是一个受控/非受控皆可的布尔切换控件,底层基于 Reka UI 的SwitchRoot与SwitchThumb原语构建。它具备以下开箱即用的能力:
- 双状态切换:checked / unchecked,通过
model-value与update:model-value事件完成受控绑定; - 键盘与焦点支持:
focus-visible态样式内置,支持 Tab 聚焦与空格/回车切换; - 无障碍:内部由 Reka UI 处理
role="switch"、aria-checked等语义属性,并可透传id、aria-label等属性; - 禁用状态:
disabled时禁用点击并降低透明度(opacity-50); - 插槽扩展:
thumb插槽允许在滑块内渲染自定义内容(如图标、加载态)。
从文档 frontmatter 可以确认的信息
官方文档的 frontmatter 明确了组件的定位与技术来源:
--- title: Switch description: A control that allows the user to toggle between checked and not checked. source: apps/www/src/registry/default/ui/switch primitive: https://www.reka-ui.com/docs/components/switch.html ---source指向组件源码目录,即仓库中的 deprecated/www/src/registry/default/ui/switch(当前 v4 版本对应 apps/v4/registry/bases/reka/ui/switch);primitive标明其基础原语来自 Reka UI 的 Switch 组件,shadcn-vue 负责在其之上做样式与 DX 封装。
安装
官方文档提供了两种安装方式:CLI 一键安装与手动安装。
方式一:CLI 安装(推荐)
在项目根目录执行:
npx shadcn-vue@latest add switchCLI 会自动完成以下工作:
- 解析当前项目已有的 shadcn-vue 配置;
- 将 Switch 组件源码(
Switch.vue与index.ts)复制到项目的components/ui/switch目录; - 自动安装运行时依赖
reka-ui(如尚未安装); - 将组件注册到项目的组件索引中,使其可被自动导入。
仓库中index.ts的导出方式(deprecated/www/src/registry/default/ui/switch/index.ts)验证了最终安装形态:
export { default as Switch } from "./Switch.vue"方式二:手动安装
如果你希望完全掌控代码,或项目不便使用 CLI,可按官方文档的步骤手动安装。
第一步:安装运行时依赖
npm install reka-uireka-ui是 Switch 的底层原语库,提供SwitchRoot、SwitchThumb、SwitchRootProps、SwitchRootEmits等核心导出。
第二步:复制组件代码
将以下源码复制到项目的components/ui/switch/Switch.vue(以 v4 的 new-york 风格为例,完整文件见 apps/v4/registry/new-york-v4/ui/switch/Switch.vue):
<script setup lang="ts"> import type { SwitchRootEmits, SwitchRootProps } from "reka-ui" import type { HTMLAttributes } from "vue" import { reactiveOmit } from "@vueuse/core" import { SwitchRoot, SwitchThumb, useForwardPropsEmits, } from "reka-ui" import { cn } from "@/lib/utils" const props = defineProps<SwitchRootProps & { class?: HTMLAttributes["class"] }>() const emits = defineEmits<SwitchRootEmits>() const delegatedProps = reactiveOmit(props, "class") const forwarded = useForwardPropsEmits(delegatedProps, emits) </script> <template> <SwitchRoot v-slot="slotProps" ><script setup lang="ts"> import { Switch } from '@/components/ui/switch' </script> <template> <Switch /> </template>官方文档给出的首个示例即是这种最简形式——不带任何属性,使用非受控模式,点击后内部自动维护状态。
与 Label 组合使用
实际界面中开关几乎总是配有一个文字说明。推荐将Switch与Label组合,通过id/for建立关联(对应演示组件 apps/v4/components/demo/SwitchDemo.vue 与 deprecated/www/src/registry/default/examples/SwitchDemo.vue):
<script setup lang="ts"> import { Label } from '@/components/ui/label' import { Switch } from '@/components/ui/switch' </script> <template> <div class="flex items-center space-x-2"> <Switch id="airplane-mode" /> <Label for="airplane-mode">Airplane Mode</Label> </div> </template>id与for的关联带来两个直接收益:
- 点击命中区域扩大:点击 Label 文字也能切换开关;
- 无障碍提升:屏幕阅读器会将文字与开关状态正确关联。
受控模式与 v-model
Switch 完全遵循 Vue 的model-value/update:model-value约定,因此支持v-model直接绑定:
<script setup lang="ts"> import { ref } from 'vue' import { Switch } from '@/components/ui/switch' const isDark = ref(false) </script> <template> <Switch v-model="isDark" /> </template>也可以显式传入model-value并监听update:model-value事件,实现自定义逻辑。该事件类型来自 Reka UI 的SwitchRootEmits,在组件源码中通过defineEmits<SwitchRootEmits>()透传(见 apps/v4/registry/bases/reka/ui/switch/Switch.vue)。
默认选中
非受控模式下,可通过default-checked设置初始选中状态,这在仓库示例中频繁出现,例如 apps/v4/registry/bases/reka/examples/switch/SwitchDisabled.vue 中的禁用+默认选中组合:
<Switch id="switch-disabled-checked" :default-checked="true" :disabled="true" />在滑块内添加图标
官方文档单独用一个章节介绍了「在 Switch 滑块内添加图标」的进阶用法,核心是使用#thumb插槽。以明暗主题切换为例:
<template> <Switch :model-value="isDark" @update:model-value="toggleTheme"> <template #thumb> <Icon v-if="isDark" icon="lucide:moon" class="size-3" /> <Icon v-else icon="lucide:sun" class="size-3" /> </template> </Switch> </template>这段代码同时展示了三个要点:
- 受控绑定:
isDark决定开关状态,toggleTheme响应状态变化并切换主题; - 插槽内容响应状态:图标根据当前主题动态切换——暗色显示月亮、亮色显示太阳;
- 图标尺寸控制:通过
class="size-3"控制图标在滑块内的显示大小。
从源码看,thumb插槽由SwitchThumb渲染(见 apps/v4/registry/new-york-v4/ui/switch/Switch.vue 中的<slot name="thumb" v-bind="slotProps" />),并将slotProps一并透传给插槽内容,因此在插槽内可以访问到 Reka UI 提供的开关状态数据。
尺寸与禁用等扩展用法
v4 版本的 Switch 在基础组件上额外提供了size属性("sm" | "default"),源码见 apps/v4/registry/bases/reka/ui/switch/Switch.vue:
const props = withDefaults(defineProps<SwitchRootProps & { class?: HTMLAttributes["class"] size?: "sm" | "default" }>(), { size: "default", })尺寸通过data-size属性传递到根元素(<SwitchRoot><div class="flex items-center gap-2"> <Switch id="switch-size-sm" size="sm" /> <Label html-for="switch-size-sm">Small</Label> </div> <div class="flex items-center gap-2"> <Switch id="switch-size-default" size="default" /> <Label html-for="switch-size-default">Default</Label> </div>
禁用状态
通过:disabled="true"即可禁用开关,禁用后不可点击,样式上自动降低透明度(disabled:opacity-50)。仓库示例 apps/v4/registry/bases/reka/examples/switch/SwitchDisabled.vue 同时覆盖了「禁用且未选中」与「禁用且默认选中」两种场景:
<Switch id="switch-disabled-unchecked" :disabled="true" /> <Switch id="switch-disabled-checked" :default-checked="true" :disabled="true" />带描述的布局
在表单或设置页中,开关常与标题、描述文字并列展示。可以借助Field系列组件(Field、FieldContent、FieldTitle、FieldDescription、FieldLabel)实现规整的横向布局,参考 apps/v4/registry/bases/reka/examples/switch/SwitchWithDescription.vue:
<FieldLabel html-for="switch-focus-mode"> <Field orientation="horizontal"> <FieldContent> <FieldTitle>Share across devices</FieldTitle> <FieldDescription> Focus is shared across devices, and turns off when you leave the app. </FieldDescription> </FieldContent> <Switch id="switch-focus-mode" /> </Field> </FieldLabel>在表单中使用(VeeValidate + Zod)
官方文档的 Examples 章节给出了「Form」示例,对应仓库中的 deprecated/www/src/registry/default/examples/SwitchForm.vue。该示例演示了 Switch 与vee-validate、zod的完整集成,是设置页「邮件通知」类场景的典型模板。
定义表单 Schema
import { toTypedSchema } from "@vee-validate/zod" import { useForm } from "vee-validate" import * as z from "zod" const formSchema = toTypedSchema(z.object({ marketing_emails: z.boolean().default(false).optional(), security_emails: z.boolean(), }))marketing_emails:可选布尔,默认false;security_emails:必填布尔。
初始化表单
const { handleSubmit } = useForm({ validationSchema: formSchema, initialValues: { security_emails: true, }, })initialValues给security_emails设置了初始值true,对应界面上该开关默认处于开启状态。
提交逻辑
const onSubmit = handleSubmit((values) => { toast({ title: "You submitted the following values:", description: h("pre", { class: "mt-2 w-[340px] rounded-md bg-slate-950 p-4" }, h("code", { class: "text-white" }, JSON.stringify(values, null, 2))), }) })提交时把表单值 JSON 序列化后通过 toast 展示,方便验证开关状态是否正确绑定。
模板中的绑定方式
每个开关字段通过FormField的value与handleChange完成受控绑定:
<FormField v-slot="{ value, handleChange }" name="marketing_emails"> <FormItem class="flex flex-row items-center justify-between rounded-lg border p-4"> <div class="space-y-0.5"> <FormLabel class="text-base"> Marketing emails </FormLabel> <FormDescription> Receive emails about new products, features, and more. </FormDescription> </div> <FormControl> <Switch :model-value="value" @update:model-value="handleChange" /> </FormControl> </FormItem> </FormField>第二个字段security_emails还展示了「表单中的只读开关」写法:
<Switch :model-value="value" disabled aria-readonly @update:model-value="handleChange" />即通过disabled禁止用户操作、同时用aria-readonly向辅助技术声明该字段只读,但仍保留表单字段语义。
整个表单由<form class="w-full space-y-6" @submit="onSubmit">包裹,配合提交按钮即可构成完整的通知偏好设置页。
源码实现解析
Switch 组件的核心价值在于:用最薄的封装,把 Reka UI 原语的能力完整透传,同时注入 shadcn-vue 的设计系统样式。以 v4 bases 风格实现(apps/v4/registry/bases/reka/ui/switch/Switch.vue)为例,关键点有三:
- Props 合并透传:
SwitchRootProps(Reka UI 的根组件属性)+class+size共同组成组件的对外 Props,运行时通过reactiveOmit(props, "class", "size")剔除样式类与尺寸字段后,用useForwardPropsEmits统一转发给SwitchRoot; - Emits 原样转发:
SwitchRootEmits直接透传,保证update:model-value等事件与 Reka UI 语义一致; - 样式注入与状态钩子:根元素上的
data-[state=checked]:/data-[state=unchecked]:系列类直接响应 Reka UI 内部维护的状态属性,实现选中/未选中两种视觉态;data-disabled:系列类则接管禁用态的视觉反馈。
new-york 风格(apps/v4/registry/new-york-v4/ui/switch/Switch.vue)在实现上完全一致,仅样式 token 与尺寸不同(如h-[1.15rem] w-8、size-4的滑块、translate-x-[calc(100%-2px)]的位移)。这也正是 shadcn-vue 的架构特点:同一套原语逻辑,通过替换样式类即可切换视觉风格。
状态样式对照
| 状态 | 根元素样式(new-york) | 效果 |
|---|---|---|
| checked | data-[state=checked]:bg-primary | 轨道填充主题色 |
| unchecked | data-[state=unchecked]:bg-input(暗色下bg-input/80) | 轨道使用输入框底色 |
| 聚焦 | focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-3 | 焦点环提示 |
| 禁用 | disabled:cursor-not-allowed disabled:opacity-50 | 禁止点击 + 半透明 |
滑块位移
滑块(SwitchThumb)通过data-[state=checked]:translate-x-[calc(100%-2px)]实现从左侧到右侧的平滑位移——100%是滑块自身宽度,减去 2px 边框留白后恰好落到轨道右侧边缘;transition-transform保证位移动画平滑。
总结
Switch 组件是 shadcn-vue 中「薄封装 + 完整透传」设计哲学的典型代表:
- 安装:
npx shadcn-vue@latest add switch一条命令即可,也可手动安装reka-ui后复制组件代码; - 用法:支持最简非受控模式、
v-model受控模式、#thumb图标插槽、尺寸与禁用等扩展; - 表单集成:通过
FormField的value/handleChange与 VeeValidate + Zod 无缝协作; - 底层原理:所有交互逻辑由 Reka UI 原语承担,shadcn-vue 层只负责样式注入与 Props/Emits 透传。
相关资源可继续深入阅读:官方文档 deprecated/www/src/content/docs/components/switch.md、v4 基础组件源码 apps/v4/registry/bases/reka/ui/switch/Switch.vue、表单示例 deprecated/www/src/registry/default/examples/SwitchForm.vue,以及各风格的示例集合(见 apps/v4/registry/bases/reka/examples/switch)。
【免费下载链接】shadcn-vueVue port of shadcn-ui项目地址: https://gitcode.com/gh_mirrors/sh/shadcn-vue
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考