{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "base-switch",
  "title": "Base Switch",
  "description": "An accessible, highly customizable switch built on Base UI with Motion spring layout physics.",
  "dependencies": ["@base-ui/react", "motion", "class-variance-authority"],
  "registryDependencies": ["utils", "@soralabs/hooks-use-controlled-state"],
  "files": [
    {
      "path": "registry/ui/base/switch/index.tsx",
      "content": "\"use client\";\n\nimport { Switch as SwitchPrimitive } from \"@base-ui/react/switch\";\nimport { useControlledState } from \"@/hooks/use-controlled-state\";\nimport { cn } from \"@/lib/utils\";\nimport { cva } from \"class-variance-authority\";\nimport type { VariantProps } from \"class-variance-authority\";\nimport { motion, useReducedMotion } from \"motion/react\";\nimport type * as React from \"react\";\nimport { useId } from \"react\";\n\n/**\n * Spring animation parameters for the switch thumb layout transition.\n */\nconst SWITCH_SPRING = {\n  type: \"spring\",\n  stiffness: 500,\n  damping: 30,\n} as const;\n\nconst switchVariants = cva(\n  \"peer group/switch focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-checked:bg-primary data-unchecked:bg-input dark:data-unchecked:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 relative inline-flex shrink-0 cursor-pointer items-center rounded-full border border-transparent p-0.5 transition-colors outline-none select-none group-has-[:focus-visible]/field-label:border-transparent group-has-[:focus-visible]/field-label:ring-0 after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:ring-3 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:ring-3 data-checked:justify-end data-disabled:cursor-not-allowed data-disabled:opacity-50 data-unchecked:justify-start data-[state=checked]:justify-end data-[state=unchecked]:justify-start\",\n  {\n    variants: {\n      size: {\n        sm: \"h-5 w-9\",\n        default: \"h-6 w-11\",\n        lg: \"h-7 w-13\",\n      },\n    },\n    defaultVariants: {\n      size: \"default\",\n    },\n  }\n);\n\nconst switchThumbVariants = cva(\n  \"bg-background data-checked:bg-primary-foreground dark:data-checked:bg-primary-foreground dark:data-unchecked:bg-foreground pointer-events-none relative flex items-center justify-center rounded-full shadow-xs ring-0 select-none [&_svg]:size-3\",\n  {\n    variants: {\n      size: {\n        sm: \"size-3.5 text-[10px]\",\n        default: \"size-4.5 text-xs\",\n        lg: \"size-5.5 text-sm\",\n      },\n    },\n    defaultVariants: {\n      size: \"default\",\n    },\n  }\n);\n\ninterface SwitchProps\n  extends\n    Omit<\n      React.ComponentProps<typeof SwitchPrimitive.Root>,\n      \"render\" | \"children\"\n    >,\n    VariantProps<typeof switchVariants> {\n  /**\n   * Optional icon rendered in the track when checked (left side).\n   */\n  checkedIcon?: React.ReactNode;\n  /**\n   * Optional helper description rendered beneath the label.\n   */\n  description?: React.ReactNode;\n  /**\n   * Additional CSS classes for the description text.\n   */\n  descriptionClassName?: string;\n  /**\n   * Optional label text rendered beside the switch.\n   */\n  label?: React.ReactNode;\n  /**\n   * Additional CSS classes for the label text.\n   */\n  labelClassName?: string;\n  /**\n   * Position of the label relative to the switch.\n   * @default \"right\"\n   */\n  labelPosition?: \"left\" | \"right\";\n  /**\n   * Additional CSS classes for the thumb element.\n   */\n  thumbClassName?: string;\n  /**\n   * Optional icon or node rendered inside the moving thumb.\n   */\n  thumbIcon?: React.ReactNode;\n  /**\n   * Optional icon rendered in the track when unchecked (right side).\n   */\n  uncheckedIcon?: React.ReactNode;\n}\n\n/**\n * An accessible, highly customizable Switch component built with Base UI and Motion.\n * Features spring layout physics (`stiffness: 500, damping: 30`) and full Tailwind CSS override support.\n */\nfunction Switch({\n  checked: controlledChecked,\n  defaultChecked = false,\n  onCheckedChange,\n  size = \"default\",\n  thumbIcon,\n  checkedIcon,\n  uncheckedIcon,\n  label,\n  description,\n  labelPosition = \"right\",\n  className,\n  thumbClassName,\n  labelClassName,\n  descriptionClassName,\n  id,\n  ref,\n  disabled = false,\n  style,\n  ...props\n}: SwitchProps) {\n  const generatedId = useId();\n  const inputId = id ?? generatedId;\n  const descriptionId = description ? `${inputId}-desc` : undefined;\n\n  const [isChecked, setIsChecked] = useControlledState({\n    defaultValue: defaultChecked,\n    onChange: onCheckedChange,\n    value: controlledChecked,\n  });\n\n  const prefersReducedMotion = useReducedMotion();\n\n  const control = (\n    <SwitchPrimitive.Root\n      aria-describedby={descriptionId}\n      checked={isChecked}\n      className={cn(switchVariants({ size }), className)}\n      data-size={size}\n      data-slot=\"switch\"\n      disabled={disabled}\n      id={inputId}\n      onCheckedChange={setIsChecked}\n      onMouseDown={(e) => {\n        if (e.detail > 1) {\n          e.preventDefault();\n        }\n        props.onMouseDown?.(e);\n      }}\n      ref={ref}\n      style={{\n        justifyContent: isChecked ? \"flex-end\" : \"flex-start\",\n        ...style,\n      }}\n      {...props}\n    >\n      {checkedIcon && (\n        <motion.span\n          animate={\n            isChecked ? { opacity: 1, scale: 1 } : { opacity: 0, scale: 0.5 }\n          }\n          aria-hidden=\"true\"\n          className=\"text-primary-foreground pointer-events-none absolute left-1 flex items-center justify-center [&_svg]:size-3\"\n          initial={false}\n          transition={\n            prefersReducedMotion ? { duration: 0 } : { duration: 0.15 }\n          }\n        >\n          {checkedIcon}\n        </motion.span>\n      )}\n\n      {uncheckedIcon && (\n        <motion.span\n          animate={\n            isChecked ? { opacity: 0, scale: 0.5 } : { opacity: 1, scale: 1 }\n          }\n          aria-hidden=\"true\"\n          className=\"text-muted-foreground pointer-events-none absolute right-1 flex items-center justify-center [&_svg]:size-3\"\n          initial={false}\n          transition={\n            prefersReducedMotion ? { duration: 0 } : { duration: 0.15 }\n          }\n        >\n          {uncheckedIcon}\n        </motion.span>\n      )}\n\n      <SwitchPrimitive.Thumb\n        className={cn(switchThumbVariants({ size }), thumbClassName)}\n        data-slot=\"switch-thumb\"\n        render={\n          <motion.span\n            layout={!prefersReducedMotion}\n            transition={prefersReducedMotion ? { duration: 0 } : SWITCH_SPRING}\n          >\n            {thumbIcon}\n          </motion.span>\n        }\n      />\n    </SwitchPrimitive.Root>\n  );\n\n  if (\n    (label === null || label === undefined) &&\n    (description === null || description === undefined)\n  ) {\n    return control;\n  }\n\n  const labelContent = (\n    <div className=\"flex flex-col gap-1\">\n      {label && (\n        <label\n          className={cn(\n            \"cursor-pointer text-sm leading-none font-medium select-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70\",\n            disabled && \"cursor-not-allowed opacity-50\",\n            labelClassName\n          )}\n          htmlFor={inputId}\n        >\n          {label}\n        </label>\n      )}\n      {description && (\n        <p\n          className={cn(\n            \"text-muted-foreground text-xs leading-normal select-none\",\n            disabled && \"opacity-50\",\n            descriptionClassName\n          )}\n          id={descriptionId}\n        >\n          {description}\n        </p>\n      )}\n    </div>\n  );\n\n  return (\n    <div\n      className={cn(\n        \"inline-flex items-center gap-3\",\n        labelPosition === \"left\" && \"flex-row-reverse justify-end\"\n      )}\n    >\n      {control}\n      {labelContent}\n    </div>\n  );\n}\n\nexport type { SwitchProps };\nexport { SWITCH_SPRING, Switch, switchThumbVariants, switchVariants };\n",
      "type": "registry:ui",
      "target": "components/sora-ui/base/switch.tsx"
    }
  ],
  "meta": {
    "agentMetadata": {
      "intent": "Toggle a binary on/off setting or preference.",
      "role": "input",
      "a11yConstraints": [
        "Preserve the switch role, label association, and keyboard toggling via Space or Enter."
      ],
      "motionEngine": "Motion layout spring physics with stiffness: 500, damping: 30.",
      "compositionRules": [
        "Pair with a visible label and optional description.",
        "Provide clear visual contrast between checked and unchecked states."
      ],
      "compositionRecipes": ["Compose with base/button in a settings form."]
    },
    "demoProps": {
      "Switch": {
        "label": {
          "value": "Airplane Mode"
        },
        "disabled": {
          "value": false
        }
      }
    }
  },
  "type": "registry:ui"
}
