Radio Group
nexvyn/radio-groupFreeComponent
A radio group with roving tabindex and a dot morph selection indicator.
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/radio-group.jsonSource
1'use client'23import {4 createContext,5 forwardRef,6 useCallback,7 useContext,8 useEffect,9 useId,10 useMemo,11 useRef,12 useState,13 type KeyboardEvent,14 type ReactNode,15} from 'react'16import { motion, useReducedMotion } from 'motion/react'17import { cn } from '@/lib/utils'18import { springs } from '@/lib/motion-tokens'19import { playHoverSound, playClickSound } from '@/lib/sound'2021interface RadioGroupContextValue {22 value: string | undefined23 setValue: (v: string) => void24 name?: string25 groupId: string26 focusedValue: string | null27 setFocusedValue: (v: string) => void28 itemValues: string[]29 registerItem: (v: string) => void30 unregisterItem: (v: string) => void31 required?: boolean32 ariaInvalid?: boolean33}3435const RadioGroupContext = createContext<RadioGroupContextValue | null>(null)3637function useRadioGroupCtx(componentName: string) {38 const ctx = useContext(RadioGroupContext)39 if (!ctx) throw new Error(`${componentName} must be used within RadioGroup`)40 return ctx41}4243export interface RadioGroupProps {44 value?: string45 defaultValue?: string46 onValueChange?: (value: string) => void47 name?: string48 children?: ReactNode49 className?: string50 required?: boolean51 'aria-invalid'?: boolean52 'aria-describedby'?: string53}5455export const RadioGroup = forwardRef<HTMLDivElement, RadioGroupProps>(56 (57 {58 value: valueProp,59 defaultValue,60 onValueChange,61 name,62 children,63 className,64 required,65 'aria-invalid': ariaInvalid,66 'aria-describedby': ariaDescribedby,67 ...props68 },69 ref,70 ) => {71 const isControlled = valueProp !== undefined72 const [internal, setInternal] = useState(defaultValue)73 const value = isControlled ? valueProp : internal74 const groupId = useId()75 const containerRef = useRef<HTMLDivElement>(null)7677 const [itemValues, setItemValues] = useState<string[]>([])78 const [focusedValue, setFocusedValue] = useState<string | null>(null)7980 const registerItem = useCallback((v: string) => {81 setItemValues((prev) => (prev.includes(v) ? prev : [...prev, v]))82 }, [])8384 const unregisterItem = useCallback((v: string) => {85 setItemValues((prev) => prev.filter((x) => x !== v))86 }, [])8788 const setValue = useCallback(89 (v: string) => {90 if (!isControlled) setInternal(v)91 onValueChange?.(v)92 setFocusedValue(v)93 },94 [isControlled, onValueChange],95 )9697 const handleKeyDown = useCallback(98// … truncated
What it pulls in
npm packages
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 |
