{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "radio-group",
  "title": "Radio Group",
  "type": "registry:ui",
  "files": [
    {
      "path": "packages/registry-react/components/radio-group/radio-group.tsx",
      "content": "'use client'\n\nimport * as React from 'react'\nimport * as RadioGroupPrimitive from '@radix-ui/react-radio-group'\nimport { Circle } from 'lucide-react'\nimport { cn } from '@/lib/utils'\n\nexport type RadioOption = string | { label: string; value: string; disabled?: boolean }\n\n// Make group-level config (disabled / size / optionType / buttonVariant /\n// orientation) reachable from items without manual prop drilling — the React\n// equivalent of the Vue provide('radioGroup').\ntype RadioGroupContextValue = {\n  disabled?: boolean\n  size?: 'small' | 'middle' | 'large'\n  optionType?: 'default' | 'button'\n  buttonVariant?: 'outline' | 'solid'\n  orientation?: 'horizontal' | 'vertical'\n}\n\nconst RadioGroupContext = React.createContext<RadioGroupContextValue>({})\n\nfunction normalizeOption(option: RadioOption): { label: string; value: string; disabled?: boolean } {\n  if (typeof option === 'string') {\n    return { label: option, value: option }\n  }\n  return option\n}\n\nexport interface RadioGroupProps\n  extends Omit<React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Root>, 'defaultValue' | 'dir'> {\n  /** The controlled value of the radio items to check. */\n  value?: string\n  /** The value of the radio items that should be checked when initially rendered. */\n  defaultValue?: string\n  /** When `true`, prevents the user from interacting with the radio group */\n  disabled?: boolean\n  /** The orientation of the radio items */\n  orientation?: 'horizontal' | 'vertical'\n  /** When `true`, keyboard navigation will loop from last item to first, and vice versa */\n  loop?: boolean\n  /** Label for the radio group */\n  label?: string\n  /** Hint text for the radio group */\n  hint?: string\n  /** Error messages to display */\n  errorMessages?: string | string[]\n  /** Whether to show error state */\n  error?: boolean\n  /** Density of the radio items */\n  density?: 'compact' | 'default' | 'comfortable'\n  /** Whether the radio group appears flat */\n  flat?: boolean\n  /** Whether to show a border around the group */\n  bordered?: boolean\n  /** The reading direction */\n  dir?: 'ltr' | 'rtl'\n  /** Array of options to render automatically */\n  options?: RadioOption[]\n  /** Size of button-style radios */\n  size?: 'small' | 'middle' | 'large'\n  /** Type of options to render */\n  optionType?: 'default' | 'button'\n  /** Visual variant for button-style radios */\n  buttonVariant?: 'outline' | 'solid'\n}\n\n// Density classes\nconst groupDensityClasses = {\n  compact: 'gap-1',\n  default: 'gap-3',\n  comfortable: 'gap-4',\n}\n\nconst RadioGroup = React.forwardRef<React.ElementRef<typeof RadioGroupPrimitive.Root>, RadioGroupProps>(\n  (\n    {\n      className,\n      value,\n      defaultValue,\n      disabled = false,\n      orientation = 'vertical',\n      loop = true,\n      label,\n      hint,\n      errorMessages,\n      error,\n      density = 'default',\n      flat = false,\n      bordered = false,\n      dir,\n      options,\n      size = 'middle',\n      optionType = 'default',\n      buttonVariant = 'outline',\n      children,\n      ...props\n    },\n    ref,\n  ) => {\n    const hasError =\n      Boolean(error) ||\n      Boolean(errorMessages && (typeof errorMessages === 'string' ? errorMessages : errorMessages.length > 0))\n\n    return (\n      <RadioGroupContext.Provider value={{ disabled, size, optionType, buttonVariant, orientation }}>\n        {/*\n          `className` is forwarded ONLY to RadioGroupRoot below (per shadcn\n          convention). Applying it on the outer wrapper as well caused grid-*\n          utilities to fight the wrapper's `flex flex-col`, so consumers had\n          to fall back to column-count hacks. Forwarding to one element keeps\n          layout intent unambiguous.\n        */}\n        <div className=\"flex flex-col gap-2\">\n          {label && <label className=\"text-sm font-medium\">{label}</label>}\n\n          {hint && !hasError && <p className=\"text-muted-foreground text-xs\">{hint}</p>}\n\n          <RadioGroupPrimitive.Root\n            ref={ref}\n            data-slot=\"radio-group\"\n            value={value}\n            defaultValue={defaultValue}\n            disabled={disabled}\n            orientation={orientation}\n            loop={loop}\n            dir={dir}\n            className={cn(\n              'grid gap-3',\n              orientation === 'horizontal' && 'flex flex-row items-center gap-4',\n              optionType === 'button' && orientation === 'horizontal' && 'flex flex-row items-stretch gap-0',\n              optionType === 'button' && orientation === 'vertical' && 'flex flex-col items-stretch gap-0',\n              optionType !== 'button' && groupDensityClasses[density],\n              bordered && 'rounded-lg border p-4',\n              className,\n            )}\n            {...props}\n          >\n            {options && options.length > 0\n              ? optionType === 'button'\n                ? options.map((option) => {\n                    const opt = normalizeOption(option)\n                    return <RadioButton key={opt.value} value={opt.value} disabled={opt.disabled} label={opt.label} />\n                  })\n                : options.map((option) => {\n                    const opt = normalizeOption(option)\n                    return (\n                      <div key={opt.value} className=\"flex items-center gap-2\">\n                        <RadioGroupItem id={opt.value} value={opt.value} disabled={opt.disabled} />\n                        <label\n                          htmlFor={opt.value}\n                          className={cn(\n                            'cursor-pointer text-sm font-medium select-none',\n                            opt.disabled && 'cursor-not-allowed opacity-50',\n                          )}\n                        >\n                          {opt.label}\n                        </label>\n                      </div>\n                    )\n                  })\n              : children}\n          </RadioGroupPrimitive.Root>\n\n          {hasError && (\n            <div className=\"flex flex-col gap-0.5\">\n              {typeof errorMessages === 'string' ? (\n                <p className=\"text-destructive text-xs\">{errorMessages}</p>\n              ) : (\n                errorMessages?.map((msg, i) => (\n                  <p key={i} className=\"text-destructive text-xs\">\n                    {msg}\n                  </p>\n                ))\n              )}\n            </div>\n          )}\n        </div>\n      </RadioGroupContext.Provider>\n    )\n  },\n)\nRadioGroup.displayName = 'RadioGroup'\n\nexport interface RadioGroupItemProps\n  extends Omit<React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item>, 'children'> {\n  /** Size of the radio item */\n  size?: 'sm' | 'md' | 'lg'\n  /** Custom color for the checked state */\n  color?: 'primary' | 'secondary' | 'success' | 'warning' | 'error' | 'info' | string\n  /** Label text displayed next to the radio item */\n  label?: string\n  /** Hint text shown below the radio item */\n  hint?: string\n  /** Error message to display */\n  errorMessages?: string | string[]\n  /** Whether to show error state */\n  error?: boolean\n  /** Density of the radio item */\n  density?: 'compact' | 'default' | 'comfortable'\n  /** Label position - before or after the radio */\n  labelPosition?: 'before' | 'after'\n  /** Loading state */\n  loading?: boolean\n  /** Hide the indicator icon */\n  hideIcon?: boolean\n  children?: React.ReactNode\n}\n\n// Size classes\nconst sizeClasses = {\n  sm: 'size-3.5',\n  md: 'size-4',\n  lg: 'size-5',\n}\n\nconst indicatorSizes = {\n  sm: 'size-1.5',\n  md: 'size-2',\n  lg: 'size-2.5',\n}\n\n// Color classes\nconst colorClasses: Record<string, string> = {\n  primary: 'data-[state=checked]:border-primary',\n  secondary: 'data-[state=checked]:border-secondary',\n  success: 'data-[state=checked]:border-[var(--success)] data-[state=checked]:text-[var(--success)]',\n  warning: 'data-[state=checked]:border-[var(--warning)] data-[state=checked]:text-[var(--warning)]',\n  error: 'data-[state=checked]:border-destructive data-[state=checked]:text-destructive',\n  info: 'data-[state=checked]:border-[var(--info)] data-[state=checked]:text-[var(--info)]',\n}\n\n// Density classes\nconst itemDensityClasses = {\n  compact: 'gap-1',\n  default: 'gap-2',\n  comfortable: 'gap-3',\n}\n\nconst RadioGroupItem = React.forwardRef<React.ElementRef<typeof RadioGroupPrimitive.Item>, RadioGroupItemProps>(\n  (\n    {\n      className,\n      id,\n      value,\n      disabled,\n      size = 'md',\n      color = 'primary',\n      label,\n      hint,\n      errorMessages,\n      error,\n      density = 'default',\n      labelPosition = 'after',\n      loading,\n      hideIcon = false,\n      children,\n      ...props\n    },\n    ref,\n  ) => {\n    const groupContext = React.useContext(RadioGroupContext)\n    const effectiveDisabled = disabled ?? groupContext.disabled ?? false\n\n    const hasError =\n      Boolean(error) ||\n      Boolean(errorMessages && (typeof errorMessages === 'string' ? errorMessages : errorMessages.length > 0))\n\n    const labelClasses = cn(\n      'cursor-pointer text-sm leading-none font-medium select-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70',\n      hasError ? 'text-destructive' : '',\n      effectiveDisabled && 'cursor-not-allowed opacity-50',\n    )\n\n    return (\n      <div className={cn('flex items-start', itemDensityClasses[density])}>\n        {label && labelPosition === 'before' && (\n          <label htmlFor={id} className={cn('mr-2', labelClasses)}>\n            {label}\n          </label>\n        )}\n\n        <div className=\"flex items-center\">\n          <RadioGroupPrimitive.Item\n            ref={ref}\n            id={id}\n            data-uipkge=\"\"\n            data-slot=\"radio-group-item\"\n            value={value!}\n            disabled={effectiveDisabled}\n            className={cn(\n              'border-input text-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 aspect-square shrink-0 rounded-full border shadow-sm transition-colors duration-200 outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50',\n              sizeClasses[size],\n              colorClasses[color] || colorClasses.primary,\n              hasError && 'border-destructive',\n              className,\n            )}\n            {...props}\n          >\n            <RadioGroupPrimitive.Indicator\n              data-uipkge=\"\"\n              data-slot=\"radio-group-indicator\"\n              className=\"relative flex items-center justify-center\"\n            >\n              {children ??\n                (!hideIcon ? (\n                  <Circle\n                    className={cn(\n                      'absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 fill-current text-current',\n                      indicatorSizes[size],\n                    )}\n                  />\n                ) : null)}\n            </RadioGroupPrimitive.Indicator>\n          </RadioGroupPrimitive.Item>\n\n          {label && labelPosition === 'after' && (\n            <label htmlFor={id} className={cn('ml-2', labelClasses)}>\n              {label}\n            </label>\n          )}\n        </div>\n\n        {hint && !hasError && <p className=\"text-muted-foreground mt-1 ml-6 text-xs\">{hint}</p>}\n\n        {hasError && (\n          <div className=\"mt-1 ml-6 flex flex-col gap-0.5\">\n            {typeof errorMessages === 'string' ? (\n              <p className=\"text-destructive text-xs\">{errorMessages}</p>\n            ) : (\n              errorMessages?.map((msg, i) => (\n                <p key={i} className=\"text-destructive text-xs\">\n                  {msg}\n                </p>\n              ))\n            )}\n          </div>\n        )}\n      </div>\n    )\n  },\n)\nRadioGroupItem.displayName = 'RadioGroupItem'\n\nexport interface RadioButtonProps\n  extends Omit<React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item>, 'children'> {\n  /** Size of the button radio */\n  size?: 'small' | 'middle' | 'large'\n  /** Visual variant */\n  variant?: 'outline' | 'solid'\n  /** Label text */\n  label?: string\n  children?: React.ReactNode\n}\n\nconst buttonSizeClasses = {\n  small: 'h-7 px-2.5 text-xs',\n  middle: 'h-8 px-4 text-sm',\n  large: 'h-10 px-4.5 text-base',\n}\n\nconst buttonVariantClasses = {\n  outline: cn(\n    'border border-input bg-transparent text-foreground hover:text-foreground hover:bg-muted/50',\n    'data-[state=checked]:border-primary data-[state=checked]:text-primary',\n    'disabled:hover:bg-transparent',\n  ),\n  solid: cn(\n    'border border-input bg-transparent text-foreground hover:text-foreground hover:bg-muted/50',\n    'data-[state=checked]:border-primary data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground',\n    'disabled:hover:bg-transparent',\n  ),\n}\n\nconst RadioButton = React.forwardRef<React.ElementRef<typeof RadioGroupPrimitive.Item>, RadioButtonProps>(\n  ({ className, value, disabled, size, variant, label, children, ...props }, ref) => {\n    const context = React.useContext(RadioGroupContext)\n    const effectiveSize = size ?? context.size ?? 'middle'\n    const effectiveVariant = variant ?? context.buttonVariant ?? 'outline'\n    const effectiveDisabled = disabled ?? context.disabled ?? false\n\n    const groupClasses =\n      context.orientation === 'vertical'\n        ? 'rounded-md w-full justify-start'\n        : cn('rounded-none first:rounded-l-md last:rounded-r-md', 'border-l-0 first:border-l', '-ml-px first:ml-0')\n\n    return (\n      <RadioGroupPrimitive.Item\n        ref={ref}\n        data-uipkge=\"\"\n        data-slot=\"radio-button\"\n        value={value!}\n        disabled={effectiveDisabled}\n        className={cn(\n          'inline-flex items-center justify-center gap-2 font-medium whitespace-nowrap transition-colors duration-200',\n          'focus-visible:border-ring focus-visible:ring-ring/50 outline-none focus-visible:ring-[3px]',\n          'disabled:cursor-not-allowed disabled:opacity-50',\n          buttonSizeClasses[effectiveSize],\n          buttonVariantClasses[effectiveVariant],\n          groupClasses,\n          className,\n        )}\n        {...props}\n      >\n        {children ?? (label ?? value)}\n      </RadioGroupPrimitive.Item>\n    )\n  },\n)\nRadioButton.displayName = 'RadioButton'\n\nexport { RadioGroup, RadioGroupItem, RadioButton }\n",
      "type": "registry:ui",
      "target": "~/components/ui/radio-group/radio-group.tsx"
    },
    {
      "path": "packages/registry-react/components/radio-group/index.ts",
      "content": "export {\n  RadioGroup,\n  RadioGroupItem,\n  RadioButton,\n  type RadioGroupProps,\n  type RadioGroupItemProps,\n  type RadioButtonProps,\n  type RadioOption,\n} from './radio-group'\n",
      "type": "registry:ui",
      "target": "~/components/ui/radio-group/index.ts"
    }
  ],
  "dependencies": [
    "lucide-react",
    "@radix-ui/react-radio-group"
  ],
  "devDependencies": [],
  "registryDependencies": [],
  "description": "Single-selection group of radio inputs. Vertical or horizontal layout, optional descriptions per item, and full keyboard navigation. Pair with Form for validation messages.",
  "categories": [
    "form"
  ]
}