Radio Group
fluid-functionalism/radio-groupFreeComponent
Animated radio group with proximity hover, spring-animated selection dot, and optional Radix UI integration. Supports both controlled index and value-based APIs.
Install it
npx shadcn@latest add https://fluidfunctionalism.com/r/radio-group.jsonSource
1"use client";23import {4 Children,5 useRef,6 useState,7 useEffect,8 createContext,9 useContext,10 forwardRef,11 isValidElement,12 type ReactNode,13 type HTMLAttributes,14} from "react";15import { motion, AnimatePresence } from "framer-motion";16import * as RadioGroupPrimitive from "@radix-ui/react-radio-group";17import { cn } from "@/lib/utils";18import { spring } from "@/lib/springs";19import { fontWeights } from "@/lib/font-weight";20import { useProximityHover } from "@/hooks/use-proximity-hover";21import { useShape } from "@/lib/shape-context";2223interface RadioGroupContextValue {24 registerItem: (index: number, element: HTMLElement | null) => void;25 activeIndex: number | null;26 selectedIndex: number | null;27 selectedValue?: string;28 onValueChange?: (value: string) => void;29 /** Whether any item in the group is currently selected. Drives the roving30 * tabindex fallback: with no selection, the first item must stay tabbable31 * or the whole group becomes unreachable by keyboard. */32 hasSelection: boolean;33}3435const RadioGroupContext = createContext<RadioGroupContextValue | null>(null);3637function useRadioGroupContext() {38 const ctx = useContext(RadioGroupContext);39 if (!ctx) throw new Error("useRadioGroup must be used within a RadioGroup");40 return ctx;41}4243interface RadioGroupProps extends Omit<HTMLAttributes<HTMLDivElement>, "onSelect"> {44 children: ReactNode;45 selectedIndex?: number;46 value?: string;47 onValueChange?: (value: string) => void;48}4950const RadioGroup = forwardRef<HTMLDivElement, RadioGroupProps>(51 ({ children, selectedIndex, value, onValueChange, className, ...props }, ref) => {52 const containerRef = useRef<HTMLDivElement>(null);53 const childValues = Children.toArray(children)54 .filter(isValidElement)55 .map((child) => (child.props as { value?: string }).value);56 const {57 activeIndex,58 setActiveIndex,59 itemRects,60 sessionRef,61 handlers,62 registerItem,63 measureItems,64 } = useProximityHover(containerRef);6566 useEffect(() => {67 measureItems();68 }, [measureItems, children]);6970 const [focusedIndex, setFocusedIndex] = useState<number | null>(null);71 const resolvedSelectedIndex =72 value !== undefined73 ? childValues.findIndex((childValue) => childValue === value)74 : selectedIndex ?? -1;75 // Covers all three selection APIs: value, selectedIndex, per-item selected.76 const hasSelection =77 resolvedSelectedIndex >= 0 ||78// … truncated
What it pulls in
npm packages
Other registry items
Files it writes
More from fluid-functionalism
All 53 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Accordionaccordion | fluid-functionalism | Components | Free | 3 deps | |
| Accordion (Base UI)accordion-base | fluid-functionalism | Components | Free | 3 deps | |
| Ask User Questionsask-user-questions | fluid-functionalism | Components | Free | 2 deps | |
| Badgebadge | fluid-functionalism | Components | Free | 1 dep | |
| Buttonbutton | fluid-functionalism | Components | Free | 4 deps | |
| Button (Base UI)button-base | fluid-functionalism | Components | Free | 4 deps | |
| Cardcard | fluid-functionalism | Components | Free | 1 dep | |
| Chat Messagechat-message | fluid-functionalism | Components | Free | 1 dep |
