{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "input-otp",
  "type": "registry:ui",
  "registryDependencies": [],
  "dependencies": ["@base-ui/react", "motion"],
  "devDependencies": [],
  "files": [
    {
      "path": "input-otp.tsx",
      "content": "\"use client\";\n\nimport type { OTPFieldInput, OTPFieldRoot } from \"@base-ui/react/otp-field\";\nimport { OTPFieldPreview as OTPField } from \"@base-ui/react/otp-field\";\nimport { AnimatePresence, motion, useReducedMotion } from \"motion/react\";\nimport {\n  createContext,\n  Fragment,\n  forwardRef,\n  type ReactNode,\n  useContext,\n  useId,\n  useMemo,\n} from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst DEFAULT_OTP_LENGTH = 6;\nconst MAX_OTP_LENGTH = 12;\n\nconst controlCornerClassName =\n  \"rounded-[min(var(--radius-md),12px)] supports-[corner-shape:squircle]:corner-squircle supports-[corner-shape:squircle]:rounded-[14px]\";\n\nconst componentThemeClassName =\n  \"[--ic-background:#ffffff] [--ic-foreground:#111111] [--ic-primary:#111111] [--ic-secondary:#646b75] [--ic-surface-border:#e9edf2] [--ic-border:#e3e7ec] [--ic-card:#ffffff] [--ic-card-foreground:#111111] [--ic-muted:#f5f7fa] [--ic-muted-foreground:#6d7480] [--ic-accent:#f3f5f8] [--color-accent:var(--ic-accent)] [--color-accent-foreground:var(--ic-accent-foreground)] [--ic-accent-foreground:#111111] [--ic-input:#e3e7ec] [--ic-ring:rgba(17,17,17,0.16)] [--ic-destructive:#dc2626] [--ic-paper:#fcfcfd] [--ic-popover-foreground:#111111] [--ic-brand:#0ea5e9] [--ic-brand-soft:#bae6fd] [--ic-shadow-soft:0_18px_38px_-24px_rgba(15,23,42,0.35)] [--ic-chart-1:oklch(0.52_0.19_254)] [--ic-chart-2:oklch(0.74_0.11_232)] [--ic-chart-3:oklch(0.42_0.16_262)] [--ic-chart-4:oklch(0.84_0.07_228)] [--ic-chart-5:oklch(0.62_0.14_240)] [--color-background:var(--ic-background)] [--color-foreground:var(--ic-foreground)] [--color-primary:var(--ic-primary)] [--color-secondary:var(--ic-secondary)] [--color-border:var(--ic-border)] [--color-card:var(--ic-card)] [--color-card-foreground:var(--ic-card-foreground)] [--color-muted:var(--ic-muted)] [--color-muted-foreground:var(--ic-muted-foreground)] [--color-accent:var(--ic-accent)] [--color-accent-foreground:var(--ic-accent-foreground)] [--color-input:var(--ic-input)] [--color-ring:var(--ic-ring)] [--color-destructive:var(--ic-destructive)] [--color-paper:var(--ic-paper)] [--color-popover-foreground:var(--ic-popover-foreground)] [--color-brand:var(--ic-brand)] [--color-brand-soft:var(--ic-brand-soft)] [--color-chart-1:var(--ic-chart-1)] [--color-chart-2:var(--ic-chart-2)] [--color-chart-3:var(--ic-chart-3)] [--color-chart-4:var(--ic-chart-4)] [--color-chart-5:var(--ic-chart-5)] dark:[--ic-background:#111111] dark:[--ic-foreground:#f6f3ec] dark:[--ic-primary:#f6f3ec] dark:[--ic-secondary:#cbc6bb] dark:[--ic-surface-border:#2a2a25] dark:[--ic-border:#2b2a25] dark:[--ic-card:#111111] dark:[--ic-card-foreground:#f6f3ec] dark:[--ic-muted:#171716] dark:[--ic-muted-foreground:#9a958a] dark:[--ic-accent:#1a1a18] [--color-accent:var(--ic-accent)] [--color-accent-foreground:var(--ic-accent-foreground)] dark:[--ic-accent-foreground:#f6f3ec] dark:[--ic-input:#2b2a25] dark:[--ic-ring:rgba(246,243,236,0.18)] dark:[--ic-destructive:#f87171] dark:[--ic-paper:#171716] dark:[--ic-popover-foreground:#f6f3ec] dark:[--ic-brand:#38bdf8] dark:[--ic-brand-soft:#0c4a6e] dark:[--ic-shadow-soft:0_20px_44px_-28px_rgba(0,0,0,0.6)] dark:[--ic-chart-1:oklch(0.68_0.17_250)] dark:[--ic-chart-2:oklch(0.82_0.09_225)] dark:[--ic-chart-3:oklch(0.58_0.15_260)] dark:[--ic-chart-4:oklch(0.75_0.12_235)] dark:[--ic-chart-5:oklch(0.88_0.06_220)]\";\n\nconst otpGroupClassName =\n  \"flex w-full max-w-full items-center justify-center gap-2 sm:gap-3\";\n\nconst otpSlotSizeClassNames = {\n  default: \"size-11 shrink-0 text-lg tabular-nums sm:size-14 sm:text-xl\",\n  sm: \"size-9 shrink-0 text-base tabular-nums sm:size-10 sm:text-lg\",\n} as const;\n\nconst otpSlotBaseClassName =\n  \"relative flex items-center justify-center overflow-hidden border-2 font-medium\";\n\nconst springTransition = {\n  type: \"spring\" as const,\n  stiffness: 400,\n  damping: 25,\n  mass: 0.8,\n};\n\nconst charSpring = {\n  type: \"spring\" as const,\n  stiffness: 500,\n  damping: 30,\n  mass: 0.8,\n};\n\nconst charMorphTransition = {\n  ...charSpring,\n  filter: { duration: 0.22, ease: [0.22, 1, 0.36, 1] as const },\n  opacity: { duration: 0.16, ease: \"easeOut\" as const },\n};\n\nconst charMorphEnter = {\n  filter: \"blur(8px)\",\n  opacity: 0.55,\n  scale: 0.82,\n  y: 10,\n};\n\nconst charMorphActive = {\n  filter: \"blur(0px)\",\n  opacity: 1,\n  scale: 1,\n  y: 0,\n};\n\nconst charMorphExit = {\n  filter: \"blur(6px)\",\n  opacity: 0,\n  scale: 0.88,\n  y: -8,\n};\n\nconst INSTANT_TRANSITION = { duration: 0 } as const;\n\ntype OTPSize = keyof typeof otpSlotSizeClassNames;\n\ntype OTPConfig = {\n  length: number;\n  mask: boolean;\n  prefersReducedMotion: boolean;\n  showInvalid: boolean;\n  size: OTPSize;\n  slotPlaceholder?: string;\n};\n\nconst OTPLengthContext = createContext<number | null>(null);\nconst OTPConfigContext = createContext<OTPConfig>({\n  length: DEFAULT_OTP_LENGTH,\n  mask: false,\n  prefersReducedMotion: false,\n  showInvalid: false,\n  size: \"default\",\n});\n\nfunction useOTPConfig() {\n  return useContext(OTPConfigContext);\n}\n\nfunction useOTPLength() {\n  const length = useContext(OTPLengthContext);\n\n  if (process.env.NODE_ENV !== \"production\" && length === null) {\n    throw new Error(\"OTP slot components must be rendered inside <OTP>.\");\n  }\n\n  return length ?? DEFAULT_OTP_LENGTH;\n}\n\nfunction setRef<T>(ref: React.Ref<T> | undefined, value: T | null) {\n  if (typeof ref === \"function\") {\n    ref(value);\n    return;\n  }\n\n  if (ref) {\n    (ref as React.MutableRefObject<T | null>).current = value;\n  }\n}\n\nfunction resolveOTPLength(length?: number, maxLength?: number) {\n  const raw = length ?? maxLength ?? DEFAULT_OTP_LENGTH;\n\n  if (!Number.isFinite(raw)) {\n    return DEFAULT_OTP_LENGTH;\n  }\n\n  const normalized = Math.floor(raw);\n\n  if (normalized < 1) {\n    return DEFAULT_OTP_LENGTH;\n  }\n\n  return Math.min(normalized, MAX_OTP_LENGTH);\n}\n\nfunction normalizeSeparators(\n  separatorAfter: number | number[] | undefined,\n  length: number\n) {\n  if (separatorAfter === undefined) {\n    return new Set<number>();\n  }\n\n  const indices = Array.isArray(separatorAfter)\n    ? separatorAfter\n    : [separatorAfter];\n\n  const normalized = indices.filter(\n    (index) => Number.isInteger(index) && index > 0 && index < length\n  );\n\n  return new Set(normalized);\n}\n\nfunction resolveAriaInvalid(\n  showInvalid: boolean,\n  ariaInvalidProp?: boolean | \"false\" | \"true\" | \"grammar\" | \"spelling\"\n) {\n  if (showInvalid) {\n    return true;\n  }\n\n  if (ariaInvalidProp === true) {\n    return true;\n  }\n\n  return ariaInvalidProp;\n}\n\nfunction getOTPFieldMeta({\n  ariaDescribedByProp,\n  ariaInvalidProp,\n  description,\n  errorMessage,\n  generatedId,\n  id,\n  invalid,\n  label,\n}: {\n  ariaDescribedByProp?: string;\n  ariaInvalidProp?: boolean | \"false\" | \"true\" | \"grammar\" | \"spelling\";\n  description?: ReactNode;\n  errorMessage?: ReactNode;\n  generatedId: string;\n  id?: string;\n  invalid: boolean;\n  label?: ReactNode;\n}) {\n  const otpId = id ?? generatedId;\n  const descriptionId = description ? `${otpId}-description` : undefined;\n  const errorId = errorMessage ? `${otpId}-error` : undefined;\n  const showInvalid =\n    invalid || Boolean(errorMessage) || ariaInvalidProp === true;\n  const hasFieldChrome = Boolean(label || description || errorMessage);\n\n  return {\n    describedBy: [ariaDescribedByProp, descriptionId, errorId]\n      .filter(Boolean)\n      .join(\" \"),\n    descriptionId,\n    errorId,\n    hasFieldChrome,\n    otpId,\n    showInvalid,\n  };\n}\n\nfunction OTPFieldWrapper({\n  children,\n  description,\n  descriptionClassName,\n  descriptionId,\n  errorId,\n  errorMessage,\n  errorMessageClassName,\n  label,\n  labelClassName,\n  otpId,\n  required,\n  wrapperClassName,\n}: {\n  children: ReactNode;\n  description?: ReactNode;\n  descriptionClassName?: string;\n  descriptionId?: string;\n  errorId?: string;\n  errorMessage?: ReactNode;\n  errorMessageClassName?: string;\n  label?: ReactNode;\n  labelClassName?: string;\n  otpId: string;\n  required: boolean;\n  wrapperClassName?: string;\n}) {\n  return (\n    <div className={cn(\"flex w-full flex-col gap-2\", wrapperClassName)}>\n      {label ? (\n        <label\n          className={cn(\"font-medium text-foreground text-sm\", labelClassName)}\n          htmlFor={otpId}\n        >\n          {label}\n          {required ? (\n            <span aria-hidden className=\"text-destructive\">\n              {\" \"}\n              *\n            </span>\n          ) : null}\n        </label>\n      ) : null}\n      {children}\n      {description ? (\n        <p\n          className={cn(\n            \"text-muted-foreground text-xs leading-snug\",\n            descriptionClassName\n          )}\n          id={descriptionId}\n        >\n          {description}\n        </p>\n      ) : null}\n      {errorMessage ? (\n        <p\n          aria-live=\"polite\"\n          className={cn(\n            \"text-destructive text-xs leading-snug\",\n            errorMessageClassName\n          )}\n          id={errorId}\n          role=\"alert\"\n        >\n          {errorMessage}\n        </p>\n      ) : null}\n    </div>\n  );\n}\n\ntype OTPProps = React.ComponentPropsWithoutRef<typeof OTPField.Root> & {\n  containerClassName?: string;\n  description?: ReactNode;\n  descriptionClassName?: string;\n  errorMessage?: ReactNode;\n  errorMessageClassName?: string;\n  invalid?: boolean;\n  label?: ReactNode;\n  labelClassName?: string;\n  /** @deprecated Use `length` instead. */\n  maxLength?: number;\n  size?: OTPSize;\n  wrapperClassName?: string;\n};\n\nconst otpRootBaseClassName =\n  \"flex w-full max-w-full items-center justify-center gap-2 data-disabled:cursor-not-allowed data-disabled:opacity-50 sm:gap-3\";\n\nfunction resolveRootClassName(\n  className: OTPProps[\"className\"],\n  containerClassName?: string\n) {\n  const baseClassName = cn(otpRootBaseClassName, containerClassName);\n\n  if (typeof className === \"function\") {\n    return (state: OTPFieldRoot.State) => cn(baseClassName, className(state));\n  }\n\n  return cn(baseClassName, className);\n}\n\nconst OTP = forwardRef<HTMLDivElement, OTPProps>(\n  (\n    {\n      \"aria-describedby\": ariaDescribedByProp,\n      \"aria-invalid\": ariaInvalidProp,\n      children,\n      className,\n      containerClassName,\n      description,\n      descriptionClassName,\n      errorMessage,\n      errorMessageClassName,\n      id,\n      invalid = false,\n      label,\n      labelClassName,\n      length,\n      mask = false,\n      maxLength,\n      required = false,\n      size = \"default\",\n      wrapperClassName,\n      ...props\n    },\n    ref\n  ) => {\n    const generatedId = useId();\n    const prefersReducedMotion = useReducedMotion() ?? false;\n    const resolvedLength = resolveOTPLength(length, maxLength);\n    const fieldMeta = getOTPFieldMeta({\n      ariaDescribedByProp,\n      ariaInvalidProp,\n      description,\n      errorMessage,\n      generatedId,\n      id,\n      invalid,\n      label,\n    });\n\n    const config = useMemo<OTPConfig>(\n      () => ({\n        length: resolvedLength,\n        mask: Boolean(mask),\n        prefersReducedMotion,\n        showInvalid: fieldMeta.showInvalid,\n        size,\n      }),\n      [fieldMeta.showInvalid, mask, prefersReducedMotion, resolvedLength, size]\n    );\n\n    const root = (\n      <div className={cn(componentThemeClassName, \"w-full\")} data-slot=\"otp\">\n        <OTPConfigContext.Provider value={config}>\n          <OTPLengthContext.Provider value={resolvedLength}>\n            <OTPField.Root\n              aria-describedby={fieldMeta.describedBy || undefined}\n              aria-invalid={resolveAriaInvalid(\n                fieldMeta.showInvalid,\n                ariaInvalidProp\n              )}\n              className={resolveRootClassName(className, containerClassName)}\n              data-slot=\"otp-root\"\n              id={fieldMeta.otpId}\n              length={resolvedLength}\n              mask={mask}\n              ref={ref}\n              required={required}\n              {...props}\n            >\n              {children}\n            </OTPField.Root>\n          </OTPLengthContext.Provider>\n        </OTPConfigContext.Provider>\n      </div>\n    );\n\n    if (!fieldMeta.hasFieldChrome) {\n      return root;\n    }\n\n    return (\n      <OTPFieldWrapper\n        description={description}\n        descriptionClassName={descriptionClassName}\n        descriptionId={fieldMeta.descriptionId}\n        errorId={fieldMeta.errorId}\n        errorMessage={errorMessage}\n        errorMessageClassName={errorMessageClassName}\n        label={label}\n        labelClassName={labelClassName}\n        otpId={fieldMeta.otpId}\n        required={required}\n        wrapperClassName={wrapperClassName}\n      >\n        {root}\n      </OTPFieldWrapper>\n    );\n  }\n);\nOTP.displayName = \"OTP\";\n\nconst OTPGroup = forwardRef<\n  React.ElementRef<\"div\">,\n  React.ComponentPropsWithoutRef<\"div\">\n>(({ className, ...props }, ref) => (\n  <div\n    className={cn(otpGroupClassName, className)}\n    data-slot=\"otp-group\"\n    ref={ref}\n    {...props}\n  />\n));\nOTPGroup.displayName = \"OTPGroup\";\n\ntype OTPSlotsProps = {\n  className?: string;\n  /** Hint shown in empty slots until the active slot is focused. */\n  placeholder?: string;\n  /**\n   * Inserts `OTPSeparator` before the slot at each zero-based index.\n   * Pass a single index or an array for multiple groups.\n   */\n  separatorAfter?: number | number[];\n  slotClassName?: string;\n};\n\nfunction OTPSlots({\n  className,\n  placeholder,\n  separatorAfter,\n  slotClassName,\n}: OTPSlotsProps) {\n  const length = useOTPLength();\n  const separators = useMemo(\n    () => normalizeSeparators(separatorAfter, length),\n    [length, separatorAfter]\n  );\n  const config = useOTPConfig();\n  const mergedConfig = useMemo(\n    () => (placeholder ? { ...config, slotPlaceholder: placeholder } : config),\n    [config, placeholder]\n  );\n\n  return (\n    <OTPConfigContext.Provider value={mergedConfig}>\n      <OTPGroup className={className}>\n        {Array.from({ length }, (_, index) => (\n          <Fragment key={`otp-slot-${index}`}>\n            {separators.has(index) ? <OTPSeparator /> : null}\n            <OTPSlot\n              aria-label={\n                index === 0 ? undefined : `Character ${index + 1} of ${length}`\n              }\n              className={slotClassName}\n              placeholder={placeholder}\n            />\n          </Fragment>\n        ))}\n      </OTPGroup>\n    </OTPConfigContext.Provider>\n  );\n}\nOTPSlots.displayName = \"OTPSlots\";\n\ntype OTPSlotProps = React.ComponentPropsWithoutRef<typeof OTPField.Input> & {\n  /** Ignored. Slot order is determined by render order with Base UI OTP Field. */\n  index?: number;\n};\n\ntype OTPSlotInputProps = React.ComponentPropsWithoutRef<\"input\"> & {\n  ref?: React.Ref<HTMLInputElement>;\n};\n\ntype OTPSlotState = OTPFieldInput.State;\n\nfunction resolveSlotClassName(\n  className: OTPSlotProps[\"className\"],\n  state: OTPSlotState\n) {\n  return typeof className === \"function\" ? className(state) : className;\n}\n\nfunction getSlotBorderColor({\n  isActive,\n  showInvalid,\n  slotInvalid,\n}: {\n  isActive: boolean;\n  showInvalid: boolean;\n  slotInvalid: boolean;\n}) {\n  if (showInvalid || slotInvalid) {\n    return \"var(--color-destructive)\";\n  }\n\n  if (isActive) {\n    return \"var(--color-primary)\";\n  }\n\n  return \"var(--color-border)\";\n}\n\nfunction OTPSlotCharacter({\n  char,\n  index,\n  prefersReducedMotion,\n  staticDisplay = false,\n}: {\n  char: string;\n  index: number;\n  prefersReducedMotion: boolean;\n  staticDisplay?: boolean;\n}) {\n  if (prefersReducedMotion || staticDisplay) {\n    return (\n      <span\n        aria-hidden\n        className=\"pointer-events-none inline-block select-none\"\n      >\n        {char}\n      </span>\n    );\n  }\n\n  return (\n    <motion.span\n      animate={charMorphActive}\n      aria-hidden\n      className=\"pointer-events-none inline-block select-none\"\n      exit={charMorphExit}\n      initial={charMorphEnter}\n      key={`${index}-${char}`}\n      style={{ willChange: \"filter, opacity, transform\" }}\n      transition={charMorphTransition}\n    >\n      {char}\n    </motion.span>\n  );\n}\n\nfunction OTPSlotCaret({\n  prefersReducedMotion,\n  size,\n}: {\n  prefersReducedMotion: boolean;\n  size: OTPSize;\n}) {\n  const caretClassName = cn(\n    \"rounded-full bg-primary\",\n    size === \"sm\" ? \"h-4 w-[2px]\" : \"h-5 w-[2px] sm:h-6\"\n  );\n\n  if (prefersReducedMotion) {\n    return (\n      <div className=\"pointer-events-none absolute inset-0 flex items-center justify-center\">\n        <div className={caretClassName} />\n      </div>\n    );\n  }\n\n  return (\n    <motion.div\n      animate={{\n        opacity: 1,\n        scaleY: 1,\n        transition: {\n          opacity: { duration: 0.15 },\n          scaleY: {\n            type: \"spring\" as const,\n            stiffness: 400,\n            damping: 25,\n          },\n        },\n      }}\n      className=\"pointer-events-none absolute inset-0 flex items-center justify-center\"\n      exit={{ opacity: 0, scaleY: 0, transition: { duration: 0.1 } }}\n      initial={{ opacity: 0, scaleY: 0 }}\n    >\n      <motion.div\n        animate={{ opacity: [1, 0.3, 1] }}\n        className={caretClassName}\n        transition={{\n          duration: 1.2,\n          ease: \"easeInOut\" as const,\n          repeat: Number.POSITIVE_INFINITY,\n        }}\n      />\n    </motion.div>\n  );\n}\n\nfunction OTPSlotPlaceholderHint({\n  hint,\n  prefersReducedMotion,\n}: {\n  hint: string;\n  prefersReducedMotion: boolean;\n}) {\n  if (prefersReducedMotion) {\n    return (\n      <span\n        aria-hidden\n        className=\"pointer-events-none select-none text-muted-foreground/50\"\n      >\n        {hint}\n      </span>\n    );\n  }\n\n  return (\n    <motion.span\n      animate={{ filter: \"blur(0px)\", opacity: 1 }}\n      aria-hidden\n      className=\"pointer-events-none select-none text-muted-foreground/50\"\n      exit={{ filter: \"blur(4px)\", opacity: 0 }}\n      initial={{ filter: \"blur(4px)\", opacity: 0 }}\n      style={{ willChange: \"filter, opacity\" }}\n      transition={{\n        filter: { duration: 0.18, ease: [0.22, 1, 0.36, 1] },\n        opacity: { duration: 0.14, ease: \"easeOut\" },\n      }}\n    >\n      {hint}\n    </motion.span>\n  );\n}\n\nfunction getOTPSlotChar(state: OTPSlotState) {\n  return state.value;\n}\n\nfunction getOTPSlotDisplayChar(char: string, mask: boolean, disabled: boolean) {\n  if (disabled || !char) {\n    return \"\";\n  }\n\n  return mask ? \"\\u2022\" : char;\n}\n\nfunction isOTPSlotActive(state: OTPSlotState, tabIndex: number | undefined) {\n  return !(state.readOnly || state.disabled) && (tabIndex ?? -1) === 0;\n}\n\nfunction shouldShowOTPSlotPlaceholderHint({\n  char,\n  isActive,\n  placeholder,\n  state,\n}: {\n  char: string;\n  isActive: boolean;\n  placeholder?: string;\n  state: OTPSlotState;\n}) {\n  return (\n    Boolean(placeholder) &&\n    !char &&\n    !isActive &&\n    !state.disabled &&\n    !state.readOnly\n  );\n}\n\nfunction getOTPSlotSurfaceClassName({\n  className,\n  resolvedInvalid,\n  size,\n  state,\n}: {\n  className?: string;\n  resolvedInvalid: boolean;\n  size: OTPSize;\n  state: OTPSlotState;\n}) {\n  return cn(\n    controlCornerClassName,\n    otpSlotBaseClassName,\n    otpSlotSizeClassNames[size],\n    \"bg-background text-foreground\",\n    resolvedInvalid && \"ring-1 ring-destructive/20 dark:ring-destructive/40\",\n    state.disabled && \"cursor-not-allowed bg-muted/20\",\n    state.readOnly && \"cursor-default bg-muted/30\",\n    className\n  );\n}\n\nfunction OTPSlotSurfaceDecorations({\n  displayChar,\n  placeholder,\n  prefersReducedMotion,\n  showCaret,\n  showPlaceholderHint,\n  showStaticValue,\n  size,\n  state,\n}: {\n  displayChar: string;\n  placeholder?: string;\n  prefersReducedMotion: boolean;\n  showCaret: boolean;\n  showPlaceholderHint: boolean;\n  showStaticValue: boolean;\n  size: OTPSize;\n  state: OTPSlotState;\n}) {\n  return (\n    <>\n      <AnimatePresence\n        mode={prefersReducedMotion || showStaticValue ? undefined : \"popLayout\"}\n      >\n        {displayChar ? (\n          <OTPSlotCharacter\n            char={displayChar}\n            index={state.index}\n            prefersReducedMotion={prefersReducedMotion}\n            staticDisplay={showStaticValue}\n          />\n        ) : null}\n      </AnimatePresence>\n\n      <AnimatePresence>\n        {showPlaceholderHint && placeholder ? (\n          <OTPSlotPlaceholderHint\n            hint={placeholder}\n            prefersReducedMotion={prefersReducedMotion}\n          />\n        ) : null}\n      </AnimatePresence>\n\n      <AnimatePresence>\n        {showCaret ? (\n          <OTPSlotCaret\n            prefersReducedMotion={prefersReducedMotion}\n            size={size}\n          />\n        ) : null}\n      </AnimatePresence>\n    </>\n  );\n}\n\nfunction OTPSlotSurface({\n  className,\n  inputClassName,\n  inputRef,\n  inputProps,\n  placeholder,\n  showInvalid,\n  slotRef,\n  state,\n  tabIndex,\n}: {\n  className?: string;\n  inputClassName?: string;\n  inputProps: Omit<OTPSlotInputProps, \"className\" | \"ref\" | \"tabIndex\">;\n  inputRef: React.Ref<HTMLInputElement> | undefined;\n  placeholder?: string;\n  showInvalid: boolean;\n  slotRef: React.Ref<HTMLInputElement>;\n  state: OTPSlotState;\n  tabIndex: number | undefined;\n}) {\n  const { mask, prefersReducedMotion, size } = useOTPConfig();\n  const char = getOTPSlotChar(state);\n  const isActive = isOTPSlotActive(state, tabIndex);\n  const slotInvalid = state.valid === false;\n  const resolvedInvalid = showInvalid || slotInvalid;\n  const displayChar = getOTPSlotDisplayChar(char, mask, state.disabled);\n  const showStaticValue = state.readOnly;\n  const showCaret = isActive && !char && !state.disabled && !state.readOnly;\n  const showPlaceholderHint = shouldShowOTPSlotPlaceholderHint({\n    char,\n    isActive,\n    placeholder,\n    state,\n  });\n  const borderColor = getSlotBorderColor({\n    isActive,\n    showInvalid: resolvedInvalid,\n    slotInvalid,\n  });\n  const motionTransition = prefersReducedMotion\n    ? INSTANT_TRANSITION\n    : springTransition;\n\n  return (\n    <motion.div\n      animate={{ borderColor }}\n      className={getOTPSlotSurfaceClassName({\n        className,\n        resolvedInvalid,\n        size,\n        state,\n      })}\n      data-slot=\"otp-slot\"\n      initial={{ borderColor: \"var(--color-border)\" }}\n      transition={motionTransition}\n    >\n      <input\n        {...inputProps}\n        aria-invalid={resolvedInvalid ? true : inputProps[\"aria-invalid\"]}\n        className={cn(\n          \"absolute inset-0 z-10 cursor-text touch-manipulation opacity-0\",\n          state.disabled && \"cursor-not-allowed\",\n          state.readOnly && \"cursor-default\",\n          inputClassName\n        )}\n        placeholder={placeholder}\n        ref={(node) => {\n          setRef(inputRef, node);\n          setRef(slotRef, node);\n        }}\n        tabIndex={state.disabled ? -1 : tabIndex}\n      />\n\n      <OTPSlotSurfaceDecorations\n        displayChar={displayChar}\n        placeholder={placeholder}\n        prefersReducedMotion={prefersReducedMotion}\n        showCaret={showCaret}\n        showPlaceholderHint={showPlaceholderHint}\n        showStaticValue={showStaticValue}\n        size={size}\n        state={state}\n      />\n    </motion.div>\n  );\n}\n\nconst OTPSlot = forwardRef<HTMLInputElement, OTPSlotProps>(\n  ({ index: _index, className, placeholder, ...props }, ref) => {\n    const { showInvalid, slotPlaceholder } = useOTPConfig();\n    const resolvedPlaceholder = placeholder ?? slotPlaceholder;\n\n    if (process.env.NODE_ENV !== \"production\" && _index !== undefined) {\n      console.warn(\n        \"[OTP] The `index` prop on <OTPSlot> is ignored. Slot order is determined by render order.\"\n      );\n    }\n\n    return (\n      <OTPField.Input\n        {...props}\n        placeholder={resolvedPlaceholder}\n        render={(inputProps, state) => {\n          const {\n            className: inputClassName,\n            ref: inputRef,\n            tabIndex,\n            ...resolvedInputProps\n          } = inputProps;\n\n          return (\n            <OTPSlotSurface\n              className={resolveSlotClassName(className, state)}\n              inputClassName={inputClassName}\n              inputProps={resolvedInputProps}\n              inputRef={inputRef}\n              placeholder={resolvedPlaceholder}\n              showInvalid={showInvalid}\n              slotRef={ref}\n              state={state}\n              tabIndex={tabIndex}\n            />\n          );\n        }}\n      />\n    );\n  }\n);\nOTPSlot.displayName = \"OTPSlot\";\n\nconst OTPSeparator = forwardRef<\n  React.ElementRef<\"div\">,\n  React.ComponentPropsWithoutRef<typeof OTPField.Separator>\n>(({ className, ...props }, ref) => (\n  <OTPField.Separator\n    {...props}\n    className={cn(\"text-muted-foreground/40\", className)}\n    render={(separatorProps) => {\n      const {\n        className: separatorClassName,\n        ref: separatorRef,\n        ...resolvedSeparatorProps\n      } = separatorProps;\n\n      return (\n        <div\n          {...resolvedSeparatorProps}\n          className={cn(separatorClassName, \"pointer-events-none select-none\")}\n          data-slot=\"otp-separator\"\n          ref={(node) => {\n            setRef(separatorRef, node);\n            setRef(ref, node);\n          }}\n        >\n          <svg\n            aria-hidden\n            className=\"size-2.5 sm:size-3\"\n            fill=\"none\"\n            viewBox=\"0 0 12 12\"\n          >\n            <circle cx=\"2\" cy=\"6\" fill=\"currentColor\" r=\"1.5\" />\n            <circle cx=\"10\" cy=\"6\" fill=\"currentColor\" r=\"1.5\" />\n          </svg>\n        </div>\n      );\n    }}\n  />\n));\nOTPSeparator.displayName = \"OTPSeparator\";\n\nexport type { OTPProps, OTPSlotProps, OTPSlotsProps, OTPSize };\nexport { OTP, OTPGroup, OTPSeparator, OTPSlot, OTPSlots };\n",
      "type": "registry:ui"
    }
  ],
  "title": "Input OTP",
  "description": "Animated one-time password field built on Base UI OTP Field with focus border motion, character entrance, caret pulse, and OTPSlots auto-layout."
}
