Switch
Vue formOn/off toggle — visual analog of a hardware switch. Use for binary settings where the change takes effect immediately, not for form fields that submit later (use Checkbox there).
Also available for React ->Installation
$ pnpm dlx shadcn-vue@latest add https://uipkge.dev/r/vue/switch.json$ npx shadcn-vue@latest add https://uipkge.dev/r/vue/switch.json$ yarn dlx shadcn-vue@latest add https://uipkge.dev/r/vue/switch.json$ bunx shadcn-vue@latest add https://uipkge.dev/r/vue/switch.json
Or with the named registry:
npx shadcn-vue@latest add @uipkge/switch
Examples
Props
| Name | Type / Values | Default | Required |
|---|---|---|---|
class | HTMLAttributes['class'] | — | optional |
size | 'sm''default''lg' | — | optional |
checkedChildren | string | — | optional |
unCheckedChildren | string | — | optional |
loading | boolean | — | optional |
color | 'primary' | 'secondary' | 'success' | 'warning' | 'error' | 'info' | string | — | optional |
Dependencies
Files (2)
-
app/components/ui/switch/Switch.vue 5.3 kB
<script setup lang="ts"> import type { SwitchRootEmits, SwitchRootProps } from 'reka-ui' import type { HTMLAttributes } from 'vue' import { computed, getCurrentInstance, useSlots } from 'vue' import { reactiveOmit } from '@vueuse/core' import { SwitchRoot, SwitchThumb, useForwardPropsEmits } from 'reka-ui' import { Loader2 } from 'lucide-vue-next' import { cn } from '@/lib/utils' const props = defineProps< SwitchRootProps & { class?: HTMLAttributes['class'] size?: 'sm' | 'default' | 'lg' checkedChildren?: string unCheckedChildren?: string loading?: boolean color?: 'primary' | 'secondary' | 'success' | 'warning' | 'error' | 'info' | string } >() const emits = defineEmits<SwitchRootEmits>() const slots = useSlots() const delegatedProps = reactiveOmit( props, 'class', 'size', 'modelValue', 'checkedChildren', 'unCheckedChildren', 'loading', 'color', ) const forwarded = useForwardPropsEmits(delegatedProps, emits) // Detect controlled-vs-uncontrolled (Vue auto-defaults Boolean props to `false`, // so the prop value alone can't distinguish). const instance = getCurrentInstance() const isControlled = computed(() => Boolean(instance?.vnode?.props?.['onUpdate:modelValue'])) const userPassedModelValue = computed(() => { const raw = instance?.vnode?.props return Boolean(raw && ('modelValue' in raw || 'model-value' in raw)) }) const switchStateBindings = computed(() => { if (isControlled.value) return { 'model-value': props.modelValue } if (userPassedModelValue.value) return { 'default-value': Boolean(props.modelValue) } return { 'default-value': false } }) const hasChildren = computed(() => Boolean(props.checkedChildren || props.unCheckedChildren || slots['checked-children'] || slots['unchecked-children']), ) const sizeClasses = computed(() => { const height = { sm: 'h-4', default: 'h-[1.15rem]', lg: 'h-6', }[props.size ?? 'default'] if (hasChildren.value) { const width = { sm: 'min-w-8 w-fit', default: 'min-w-10 w-fit', lg: 'min-w-[3.25rem] w-fit', }[props.size ?? 'default'] return `${height} ${width}` } const width = { sm: 'w-6', default: 'w-8', lg: 'w-11', }[props.size ?? 'default'] return `${height} ${width}` }) const thumbSizes = { sm: 'size-3', default: 'size-4', lg: 'size-5', } const thumbTranslate = { sm: 'data-[state=checked]:translate-x-[calc(100%-2px)]', default: 'data-[state=checked]:translate-x-[calc(100%-2px)]', lg: 'data-[state=checked]:translate-x-[calc(100%-5px)]', } const textSizes = { sm: 'text-[7px]', default: 'text-xs', lg: 'text-xs', } const thumbIconSizes = { sm: 'size-2', default: 'size-2.5', lg: 'size-3', } const colorMap: Record<string, string> = { primary: 'var(--primary)', secondary: 'var(--secondary)', success: 'var(--success)', warning: 'var(--warning)', error: 'var(--destructive)', info: 'var(--info)', } const trackStyle = computed(() => ({ '--switch-checked-bg': props.color ? colorMap[props.color] || props.color : 'var(--primary)', })) </script> <template> <SwitchRoot v-slot="{ checked }" v-bind="{ 'data-slot': 'switch', ...forwarded, ...switchStateBindings }" :disabled="props.disabled || props.loading" :class=" cn( 'peer focus-visible:ring-ring/50 focus-visible:border-ring data-[state=unchecked]:bg-input dark:data-[state=unchecked]:bg-input/80 relative inline-flex shrink-0 items-center overflow-hidden rounded-full border border-transparent shadow-xs transition-[background-color,border-color,box-shadow] duration-150 outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-[var(--switch-checked-bg)]', sizeClasses, props.class, ) " :style="trackStyle" > <!-- Checked children (left side, visible when checked) --> <div v-if="hasChildren" class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-1 transition-opacity duration-150" :class="[checked ? 'opacity-100' : 'opacity-0', textSizes[size ?? 'default']]" > <span class="text-primary-foreground truncate font-medium"> <slot name="checked-children">{{ checkedChildren }}</slot> </span> </div> <!-- Unchecked children (right side, visible when unchecked) --> <div v-if="hasChildren" class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-1 transition-opacity duration-150" :class="[!checked ? 'opacity-100' : 'opacity-0', textSizes[size ?? 'default']]" > <span class="text-muted-foreground truncate font-medium"> <slot name="unchecked-children">{{ unCheckedChildren }}</slot> </span> </div> <SwitchThumb data-uipkge data-slot="switch-thumb" :class=" cn( 'bg-background dark:data-[state=unchecked]:bg-foreground dark:data-[state=checked]:bg-primary-foreground pointer-events-none z-10 block rounded-full ring-0 transition-transform duration-200 ease-[cubic-bezier(0.34,1.56,0.64,1)] data-[state=unchecked]:translate-x-0', thumbSizes[size ?? 'default'], thumbTranslate[size ?? 'default'], ) " > <Loader2 v-if="loading" :class="[thumbIconSizes[size ?? 'default'], 'text-muted-foreground animate-spin']" /> <slot v-else name="thumb" v-bind="{ checked }" /> </SwitchThumb> </SwitchRoot> </template> -
app/components/ui/switch/index.ts 0 kB
export { default as Switch } from './Switch.vue'
Raw manifest: https://uipkge.dev/r/vue/switch.json