Switch
nexvyn/switchFreeComponent
A toggle switch with thumb travel animation and hidden checkbox for forms.
Live preview
live · rendered by the registry
Rendered by nexvyn on their own page — this is the component running, not a screenshot of it.Install it
npx shadcn@latest add https://ui.nexvyn.dev/r/switch.jsonSource
1'use client'23import {4 forwardRef,5 useCallback,6 useEffect,7 useId,8 useRef,9 useState,10 type HTMLAttributes,11} from 'react'12import { animate, motion, useMotionValue, useReducedMotion } from 'motion/react'13import { cn } from '@/lib/utils'14import { springs } from '@/lib/motion-tokens'15import { playHoverSound, playClickSound } from '@/lib/sound'1617const TRACK_W = 4418const THUMB_SIZE = 2019const THUMB_TRAVEL = TRACK_W - THUMB_SIZE - 420export interface SwitchProps extends Omit<HTMLAttributes<HTMLButtonElement>, 'onChange'> {21 checked?: boolean22 defaultChecked?: boolean23 onCheckedChange?: (checked: boolean) => void24 label?: string25 labelSide?: 'left' | 'right'26 name?: string27 disabled?: boolean28 required?: boolean29 'aria-invalid'?: boolean30 'aria-describedby'?: string31}3233export const Switch = forwardRef<HTMLButtonElement, SwitchProps>(34 (35 {36 checked: checkedProp,37 defaultChecked = false,38 onCheckedChange,39 label,40 labelSide = 'right',41 disabled,42 name,43 required,44 className,45 id: idProp,46 'aria-invalid': ariaInvalid,47 'aria-describedby': ariaDescribedby,48 ...props49 },50 ref,51 ) => {52 const reactId = useId()53 const id = idProp ?? reactId54 const isControlled = checkedProp !== undefined55 const [internal, setInternal] = useState(defaultChecked)56 const value = isControlled ? checkedProp : internal57 const reduceMotion = useReducedMotion()5859 const thumbX = useMotionValue(value ? THUMB_TRAVEL : 0)60 const thumbScaleX = useMotionValue(1)6162 const prevValue = useRef(value)63 useEffect(() => {64 if (prevValue.current === value) return65 prevValue.current = value66 animate(thumbX, value ? THUMB_TRAVEL : 0, springs.settle)67 }, [value, thumbX])6869 const toggle = useCallback(() => {70 if (disabled) return71 playClickSound()72 const next = !value73 if (!isControlled) setInternal(next)74 onCheckedChange?.(next)75 }, [value, isControlled, onCheckedChange, disabled])76 const handlePointerDown = useCallback(() => {77 if (reduceMotion || disabled) return78 animate(thumbScaleX, 1.15, springs.press)79 }, [reduceMotion, disabled, thumbScaleX])8081 const handlePointerUp = useCallback(() => {82 if (reduceMotion || disabled) return83 animate(thumbScaleX, 1, springs.settle)84 }, [reduceMotion, disabled, thumbScaleX])8586 return (87 <div88 className={cn(89 'flex items-center gap-3',90// … truncated
Files it writes
More from nexvyn
All 33 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Badgebadge | nexvyn | Components | Free | 2 deps · 2 files | |
| Bars Themebars-theme | nexvyn | Components | Free | 1 dep · 3 files | |
| Bounce Sidebarbounce-sidebar | nexvyn | Components | Free | 1 dep · 2 files | |
| Breadcrumbsbreadcrumbs | nexvyn | Components | Free | 1 dep · 2 files | |
| Checkboxcheckbox | nexvyn | Components | Free | 1 dep · 3 files | |
| Clipboard Fieldclipboard-field | nexvyn | Components | Free | no deps · 2 files | |
| Color Pickercolor-picker | nexvyn | Components | Free | no deps · 16 files | |
| Comboboxcombobox | nexvyn | Components | Free | 1 dep · 2 files |
