{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "input-otp", "type": "registry:block", "title": "Input OTP", "description": "A polished OTP field for verification and two-factor auth flows.", "author": "Wensity ", "dependencies": [ "@base-ui/react", "clsx", "tailwind-merge" ], "registryDependencies": [], "files": [ { "path": "registry/wensity/lib/utils.ts", "type": "registry:lib", "target": "@lib/utils.ts", "content": "import { type ClassValue, clsx } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n" }, { "path": "registry/wensity/input-otp.tsx", "type": "registry:component", "target": "@components/wensity/input-otp.tsx", "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { OTPField } from \"@base-ui/react/otp-field\";\nimport { cn } from \"@/lib/utils\";\n\nexport type InputOtpMode = \"numeric\" | \"alphanumeric\";\nexport type InputOtpSize = \"sm\" | \"md\" | \"lg\";\nexport type InputOtpStatus = \"default\" | \"error\" | \"success\";\n\nexport interface InputOtpProps\n extends Omit<\n React.HTMLAttributes,\n \"defaultValue\" | \"onChange\"\n > {\n length?: number;\n mode?: InputOtpMode;\n masked?: boolean;\n /** Group sizes for separated layout. Must sum to `length`, e.g. `[3, 3]` → 123 • 456 */\n groups?: number[];\n /** Character or node between digit groups. Defaults to `•`. */\n separator?: React.ReactNode;\n /** When true, calls `onComplete` once all slots are filled after user input. */\n autoSubmit?: boolean;\n /** Fires when all slots are filled after user input. Requires `autoSubmit`. Never fires on mount with a prefilled value. */\n onComplete?: (value: string) => void;\n /** Fires when all slots are filled after user input, without submitting. Never fires on mount with a prefilled value. */\n onFilled?: (value: string) => void;\n value?: string;\n defaultValue?: string;\n onValueChange?: (value: string) => void;\n /** Controls success/error visuals and which message is shown. */\n status?: InputOtpStatus;\n error?: React.ReactNode;\n success?: React.ReactNode;\n label?: React.ReactNode;\n hint?: React.ReactNode;\n disabled?: boolean;\n /** Locks input without the muted disabled styling — useful for verified / post-submit display. */\n readOnly?: boolean;\n /** Shows a verifying state: locks input, sets `aria-busy`, and pulses slots. */\n loading?: boolean;\n /** Screen-reader + helper copy while `loading` is true. */\n loadingMessage?: React.ReactNode;\n inputSize?: InputOtpSize;\n fullWidth?: boolean;\n autoFocus?: boolean;\n name?: string;\n containerClassName?: string;\n slotClassName?: string;\n separatorClassName?: string;\n}\n\nconst OTP_CHECK_PATH = \"M3.25 8.1 6.35 11.2 12.75 4.6\";\nconst OTP_ERROR_PATH_A = \"M4.5 4.5 11.5 11.5\";\nconst OTP_ERROR_PATH_B = \"M11.5 4.5 4.5 11.5\";\nconst INPUT_OTP_STYLE_ID = \"wensity-input-otp-styles\";\n\nconst INPUT_OTP_STYLES = `\n[data-wensity-otp] {\n --wensity-otp-ease-morph: cubic-bezier(0.32, 0.72, 0, 1);\n --wensity-otp-ease-out: cubic-bezier(0.23, 1, 0.32, 1);\n --wensity-otp-ease-draw: cubic-bezier(0.16, 1, 0.3, 1);\n --wensity-otp-success: var(--primitive-success);\n --wensity-otp-error: var(--primitive-destructive);\n --wensity-otp-tick-delay: 220ms;\n --wensity-otp-success-message-delay: 300ms;\n}\n\n[data-wensity-otp-slot] {\n transition:\n border-color 320ms var(--wensity-otp-ease-morph),\n background-color 320ms var(--wensity-otp-ease-morph),\n box-shadow 320ms var(--wensity-otp-ease-morph),\n color 240ms var(--wensity-otp-ease-out),\n transform 320ms var(--wensity-otp-ease-morph),\n filter 260ms var(--wensity-otp-ease-draw);\n}\n\n[data-wensity-otp-slot][data-filled]:not(:focus) {\n background: color-mix(in srgb, var(--foreground) 3%, var(--background));\n}\n\n[data-wensity-otp-slot]:focus {\n border-color: color-mix(in srgb, var(--foreground) 28%, transparent);\n box-shadow:\n 0 0 0 1px color-mix(in srgb, var(--foreground) 10%, transparent),\n 0 0 0 2px var(--primitive-ring);\n}\n\n[data-wensity-otp][data-disabled] [data-wensity-otp-slot] {\n cursor: not-allowed;\n opacity: 0.45;\n}\n\n[data-wensity-otp][data-readonly] [data-wensity-otp-slot] {\n cursor: default;\n}\n\n[data-wensity-otp][data-readonly] [data-wensity-otp-slot] {\n cursor: default;\n}\n\n[data-wensity-otp][data-status=\"error\"] [data-wensity-otp-slot] {\n border-color: color-mix(in srgb, var(--wensity-otp-error) 50%, transparent);\n}\n\n[data-wensity-otp][data-status=\"error\"] [data-wensity-otp-slot]:focus {\n border-color: color-mix(in srgb, var(--wensity-otp-error) 72%, transparent);\n box-shadow: 0 0 0 1px color-mix(in srgb, var(--wensity-otp-error) 18%, transparent);\n}\n\n[data-wensity-otp][data-status=\"success\"] [data-wensity-otp-slot] {\n border-color: color-mix(in srgb, var(--wensity-otp-success) 50%, transparent);\n}\n\n[data-wensity-otp][data-status=\"success\"] [data-wensity-otp-slot]:focus {\n border-color: color-mix(in srgb, var(--wensity-otp-success) 72%, transparent);\n box-shadow: 0 0 0 1px color-mix(in srgb, var(--wensity-otp-success) 18%, transparent);\n}\n\n[data-wensity-otp][data-status=\"success\"] [data-wensity-otp-slot][data-filled] {\n background: color-mix(in srgb, var(--wensity-otp-success) 6%, var(--background));\n}\n\n@keyframes wensity-otp-shake {\n 0%,\n 100% {\n transform: translate3d(0, 0, 0);\n }\n 18% {\n transform: translate3d(-4px, 0, 0);\n }\n 36% {\n transform: translate3d(4px, 0, 0);\n }\n 54% {\n transform: translate3d(-2px, 0, 0);\n }\n 72% {\n transform: translate3d(2px, 0, 0);\n }\n}\n\n[data-wensity-otp][data-shake=\"true\"] [data-wensity-otp-slot] {\n animation: wensity-otp-shake 280ms var(--wensity-otp-ease-out);\n}\n\n[data-wensity-otp-status] {\n position: relative;\n display: inline-flex;\n width: 1.25rem;\n min-width: 1.25rem;\n margin-left: 0.25rem;\n flex: 0 0 1.25rem;\n overflow: hidden;\n align-items: center;\n justify-content: center;\n}\n\n[data-slot=\"input-otp-check\"],\n[data-slot=\"input-otp-error-a\"],\n[data-slot=\"input-otp-error-b\"] {\n fill: none;\n stroke-dasharray: 1;\n stroke-dashoffset: 1;\n opacity: 0;\n transition:\n stroke-dashoffset 140ms var(--wensity-otp-ease-draw) 0ms,\n opacity 120ms var(--wensity-otp-ease-draw) 0ms;\n}\n\n[data-wensity-otp][data-status=\"success\"] [data-slot=\"input-otp-check\"] {\n stroke-dashoffset: 0;\n opacity: 1;\n transition:\n stroke-dashoffset 280ms var(--wensity-otp-ease-draw) var(--wensity-otp-tick-delay),\n opacity 200ms var(--wensity-otp-ease-draw) var(--wensity-otp-tick-delay);\n}\n\n[data-wensity-otp][data-status=\"error\"] [data-slot=\"input-otp-error-a\"] {\n stroke-dashoffset: 0;\n opacity: 1;\n transition:\n stroke-dashoffset 240ms var(--wensity-otp-ease-draw) 0ms,\n opacity 180ms var(--wensity-otp-ease-draw) 0ms;\n}\n\n[data-wensity-otp][data-status=\"error\"] [data-slot=\"input-otp-error-b\"] {\n stroke-dashoffset: 0;\n opacity: 1;\n transition:\n stroke-dashoffset 240ms var(--wensity-otp-ease-draw) 80ms,\n opacity 180ms var(--wensity-otp-ease-draw) 80ms;\n}\n\n@keyframes wensity-otp-message-enter {\n from {\n opacity: 0;\n transform: translate3d(0, 4px, 0);\n }\n to {\n opacity: 1;\n transform: translate3d(0, 0, 0);\n }\n}\n\n[data-otp-message=\"success\"] {\n animation: wensity-otp-message-enter 180ms var(--wensity-otp-ease-morph)\n var(--wensity-otp-success-message-delay) both;\n}\n\n[data-otp-message=\"error\"],\n[data-otp-message=\"invalid\"] {\n animation: wensity-otp-message-enter 160ms var(--wensity-otp-ease-morph) 0ms both;\n}\n\n[data-otp-message=\"loading\"] {\n animation: wensity-otp-message-enter 160ms var(--wensity-otp-ease-morph) 0ms both;\n}\n\n@keyframes wensity-otp-loading-pulse {\n 0%,\n 100% {\n opacity: 1;\n }\n 50% {\n opacity: 0.58;\n }\n}\n\n[data-wensity-otp][data-loading] [data-wensity-otp-slot] {\n animation: wensity-otp-loading-pulse 1200ms var(--wensity-otp-ease-morph) infinite;\n pointer-events: none;\n}\n\n@media (prefers-reduced-motion: reduce) {\n [data-wensity-otp-slot],\n [data-otp-message],\n [data-slot=\"input-otp-check\"],\n [data-slot=\"input-otp-error-a\"],\n [data-slot=\"input-otp-error-b\"] {\n transition-duration: 0ms !important;\n transition-delay: 0ms !important;\n }\n\n [data-otp-message] {\n animation: none !important;\n opacity: 1;\n transform: none;\n }\n\n [data-wensity-otp][data-loading] [data-wensity-otp-slot],\n [data-wensity-otp][data-shake=\"true\"] [data-wensity-otp-slot] {\n animation: none !important;\n }\n\n [data-slot=\"input-otp-check\"],\n [data-slot=\"input-otp-error-a\"],\n [data-slot=\"input-otp-error-b\"] {\n stroke-dashoffset: 0;\n }\n\n [data-wensity-otp][data-status=\"success\"] [data-slot=\"input-otp-check\"],\n [data-wensity-otp][data-status=\"error\"] [data-slot=\"input-otp-error-a\"],\n [data-wensity-otp][data-status=\"error\"] [data-slot=\"input-otp-error-b\"] {\n opacity: 1;\n }\n}\n`;\n\nfunction ensureInputOtpStyles() {\n if (typeof document === \"undefined\") return;\n if (document.getElementById(INPUT_OTP_STYLE_ID)) return;\n\n const style = document.createElement(\"style\");\n style.id = INPUT_OTP_STYLE_ID;\n style.textContent = INPUT_OTP_STYLES;\n document.head.appendChild(style);\n}\n\nfunction OtpSuccessCheck() {\n return (\n \n \n \n );\n}\n\nfunction OtpErrorX() {\n return (\n \n \n \n \n );\n}\n\nconst slotSizes: Record = {\n sm: \"size-9 text-sm rounded-[var(--primitive-radius-control-sm)]\",\n md: \"size-11 text-base rounded-[var(--primitive-radius-control)]\",\n lg: \"size-[3.25rem] text-lg rounded-[var(--primitive-radius-control)]\",\n};\n\nconst separatorSizes: Record = {\n sm: \"text-sm\",\n md: \"text-base\",\n lg: \"text-lg\",\n};\n\nconst INVALID_CHAR_FEEDBACK_MS = 1600;\n\nfunction invalidCharMessage(mode: InputOtpMode): string {\n return mode === \"numeric\" ? \"Digits only.\" : \"Letters and numbers only.\";\n}\n\nfunction resolveGroups(length: number, groups?: number[]): number[] {\n if (groups?.length) {\n const sum = groups.reduce((total, size) => total + size, 0);\n if (sum === length) return groups;\n }\n return [length];\n}\n\nfunction buildGroupRanges(groups: number[]): Array<[number, number]> {\n const ranges: Array<[number, number]> = [];\n let cursor = 0;\n for (const size of groups) {\n ranges.push([cursor, cursor + size]);\n cursor += size;\n }\n return ranges;\n}\n\nexport const InputOtp = React.forwardRef(\n (\n {\n length = 6,\n mode = \"numeric\",\n masked = false,\n groups,\n separator = \"•\",\n autoSubmit = false,\n onComplete,\n onFilled,\n value,\n defaultValue = \"\",\n onValueChange,\n status: statusProp,\n error,\n success,\n label,\n hint,\n disabled = false,\n readOnly = false,\n loading = false,\n loadingMessage = \"Verifying…\",\n inputSize = \"md\",\n fullWidth = true,\n autoFocus = false,\n name,\n className,\n containerClassName,\n slotClassName,\n separatorClassName,\n id,\n \"aria-describedby\": ariaDescribedBy,\n \"aria-invalid\": ariaInvalid,\n ...props\n },\n ref,\n ) => {\n const reactId = React.useId();\n const rootId = id ?? `wensity-otp-${reactId}`;\n const labelId = label ? `${rootId}-label` : undefined;\n const hintId = hint ? `${rootId}-hint` : undefined;\n const errorId = error ? `${rootId}-error` : undefined;\n const successId = success ? `${rootId}-success` : undefined;\n const loadingId = loading ? `${rootId}-loading` : undefined;\n const invalidId = `${rootId}-invalid`;\n\n const [shake, setShake] = React.useState(false);\n const [invalidCharActive, setInvalidCharActive] = React.useState(false);\n const completedRef = React.useRef(null);\n const hasInteractedRef = React.useRef(false);\n const invalidCharTimeoutRef = React.useRef(null);\n const firstInputRef = React.useRef(null);\n\n const isFieldDisabled = disabled || loading;\n const resolvedStatus: InputOtpStatus = statusProp ?? \"default\";\n const displayStatus: InputOtpStatus = loading ? \"default\" : resolvedStatus;\n const showInvalidCharFeedback =\n invalidCharActive && displayStatus === \"default\" && !loading;\n const showHint =\n Boolean(hint) &&\n !loading &&\n !showInvalidCharFeedback &&\n displayStatus !== \"success\" &&\n displayStatus !== \"error\";\n\n React.useLayoutEffect(() => {\n ensureInputOtpStyles();\n }, []);\n\n React.useEffect(() => {\n return () => {\n if (invalidCharTimeoutRef.current !== null) {\n window.clearTimeout(invalidCharTimeoutRef.current);\n }\n };\n }, []);\n\n const pulseInvalidChar = React.useCallback(() => {\n setInvalidCharActive(true);\n if (invalidCharTimeoutRef.current !== null) {\n window.clearTimeout(invalidCharTimeoutRef.current);\n }\n invalidCharTimeoutRef.current = window.setTimeout(() => {\n setInvalidCharActive(false);\n invalidCharTimeoutRef.current = null;\n }, INVALID_CHAR_FEEDBACK_MS);\n }, []);\n\n const triggerShake = React.useCallback(() => {\n setShake(true);\n window.setTimeout(() => setShake(false), 320);\n }, []);\n\n React.useEffect(() => {\n if (loading || resolvedStatus !== \"error\") return;\n triggerShake();\n }, [loading, resolvedStatus, triggerShake]);\n\n const describedBy =\n [\n ariaDescribedBy,\n loading\n ? loadingId\n : displayStatus === \"error\" && error\n ? errorId\n : displayStatus === \"success\" && success\n ? successId\n : showInvalidCharFeedback\n ? invalidId\n : showHint\n ? hintId\n : undefined,\n ]\n .filter(Boolean)\n .join(\" \") || undefined;\n\n const groupSizes = React.useMemo(\n () => resolveGroups(length, groups),\n [groups, length],\n );\n const groupRanges = React.useMemo(\n () => buildGroupRanges(groupSizes),\n [groupSizes],\n );\n\n const validationType = mode === \"numeric\" ? \"numeric\" : \"alphanumeric\";\n const normalizeValue =\n mode === \"alphanumeric\"\n ? (next: string) => next.toUpperCase()\n : undefined;\n\n const assignFirstRef = React.useCallback(\n (node: HTMLInputElement | null) => {\n firstInputRef.current = node;\n if (typeof ref === \"function\") {\n ref(node);\n } else if (ref) {\n ref.current = node;\n }\n },\n [ref],\n );\n\n const handleValueChange = React.useCallback(\n (next: string) => {\n hasInteractedRef.current = true;\n if (next.length !== length) {\n completedRef.current = null;\n }\n onValueChange?.(next);\n },\n [length, onValueChange],\n );\n\n const handleValueComplete = React.useCallback(\n (next: string) => {\n if (!hasInteractedRef.current) return;\n if (completedRef.current === next) return;\n completedRef.current = next;\n onFilled?.(next);\n if (autoSubmit) {\n onComplete?.(next);\n }\n },\n [autoSubmit, onComplete, onFilled],\n );\n\n const handleValueInvalid = React.useCallback(() => {\n hasInteractedRef.current = true;\n pulseInvalidChar();\n }, [pulseInvalidChar]);\n\n const slotClassNameResolved = cn(\n \"relative inline-flex items-center justify-center border border-[var(--border)] bg-[var(--background)] text-center font-medium tabular-nums text-[var(--foreground)] outline-none\",\n \"placeholder:text-[var(--primitive-text-placeholder)]\",\n \"disabled:cursor-not-allowed disabled:opacity-45\",\n \"focus-visible:outline-none\",\n slotSizes[inputSize],\n slotClassName,\n );\n\n const slots = groupRanges.flatMap(([start, end], groupIndex) => {\n const groupSlots = Array.from({ length: end - start }, (_, offset) => {\n const index = start + offset;\n return (\n (\n \n )}\n />\n );\n });\n\n if (groupIndex === 0) return groupSlots;\n\n return [\n \n {separator}\n ,\n ...groupSlots,\n ];\n });\n\n return (\n
\n {label ? (\n \n {label}\n \n ) : null}\n\n \n
\n {slots}\n \n \n \n \n
\n \n\n
\n
*]:col-start-1 [&>*]:row-start-1\">\n {showHint ? (\n \n {hint}\n

\n ) : null}\n {loading ? (\n \n {loadingMessage}\n

\n ) : displayStatus === \"error\" && error ? (\n \n {error}\n

\n ) : displayStatus === \"success\" && success ? (\n \n {success}\n

\n ) : showInvalidCharFeedback ? (\n \n {invalidCharMessage(mode)}\n

\n ) : null}\n
\n
\n
\n );\n },\n);\n\nInputOtp.displayName = \"InputOtp\";\n" } ], "cssVars": { "theme": { "font-sans": "var(--font-satoshi, ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, sans-serif)", "font-display": "var(--font-cabinet, ui-sans-serif, system-ui, sans-serif)", "color-background": "var(--background)", "color-foreground": "var(--foreground)", "color-surface": "var(--surface)", "color-surface-muted": "var(--surface-muted)", "color-border": "var(--border)", "color-border-strong": "var(--border-strong)", "color-muted": "var(--muted)", "color-muted-foreground": "var(--muted-foreground)", "color-ring": "var(--ring)", "color-chili-50": "#fff1ee", "color-chili-100": "#ffe2db", "color-chili-200": "#ffa896", "color-chili-300": "#ff8a73", "color-chili-400": "#f15a45", "color-chili-500": "#cd1c18", "color-chili-600": "#b31614", "color-chili-700": "#9b1313", "color-chili-800": "#6a0d0e", "color-chili-900": "#38000a", "color-chili-950": "#1f0006", "color-primitive-surface-elevated": "var(--primitive-surface-elevated)", "color-primitive-surface-overlay": "var(--primitive-surface-overlay)", "color-primitive-surface-hover": "var(--primitive-surface-hover)", "color-primitive-surface-active": "var(--primitive-surface-active)", "color-primitive-surface-selected": "var(--primitive-surface-selected)", "color-primitive-border-subtle": "var(--primitive-border-subtle)", "color-primitive-ring": "var(--primitive-ring)", "color-primitive-text-secondary": "var(--primitive-text-secondary)", "color-primitive-text-placeholder": "var(--primitive-text-placeholder)", "color-primitive-text-inverse": "var(--primitive-text-inverse)", "color-primitive-destructive": "var(--primitive-destructive)", "color-primitive-destructive-hover": "var(--primitive-destructive-hover)", "color-primitive-destructive-active": "var(--primitive-destructive-active)", "color-primitive-destructive-foreground": "var(--primitive-destructive-foreground)", "color-primitive-destructive-surface": "var(--primitive-destructive-surface)", "color-primitive-destructive-border": "var(--primitive-destructive-border)", "color-primitive-success": "var(--primitive-success)", "color-primitive-warning": "var(--primitive-warning)", "color-primitive-info": "var(--primitive-info)", "color-primitive-control-solid": "var(--primitive-control-solid)", "color-primitive-control-solid-hover": "var(--primitive-control-solid-hover)", "color-primitive-control-solid-active": "var(--primitive-control-solid-active)", "color-primitive-control-solid-foreground": "var(--primitive-control-solid-foreground)", "color-primitive-chart-1": "var(--primitive-chart-1)", "color-primitive-chart-2": "var(--primitive-chart-2)", "color-primitive-chart-3": "var(--primitive-chart-3)", "color-primitive-chart-4": "var(--primitive-chart-4)", "color-primitive-chart-5": "var(--primitive-chart-5)", "color-primitive-chart-6": "var(--primitive-chart-6)", "radius-primitive": "var(--primitive-radius)", "radius-primitive-control": "var(--primitive-radius-control)", "radius-primitive-control-sm": "var(--primitive-radius-control-sm)", "radius-primitive-surface": "var(--primitive-radius-surface)", "radius-primitive-item": "var(--primitive-radius-item)", "font-primitive-sans": "var(--primitive-font-sans)", "font-primitive-display": "var(--primitive-font-display)", "font-primitive-mono": "var(--primitive-font-mono)", "spacing-primitive-control-height-sm": "var(--primitive-control-height-sm)", "spacing-primitive-control-height-md": "var(--primitive-control-height-md)", "spacing-primitive-control-height-lg": "var(--primitive-control-height-lg)" }, "light": { "background": "#fafafa", "foreground": "#0a0a0a", "surface": "#ffffff", "surface-muted": "#f4f4f5", "border": "rgba(10, 10, 10, 0.08)", "border-strong": "rgba(10, 10, 10, 0.16)", "muted": "#f4f4f5", "muted-foreground": "#52525b", "ring": "#cd1c18", "pattern-fg": "rgba(10, 10, 10, 0.07)", "llb-primary": "#18181b", "llb-primary-fg": "#ffffff", "llb-success": "#1f883d", "llb-error": "#cf222e", "primitive-surface-elevated": "#ffffff", "primitive-surface-overlay": "#ffffff", "primitive-surface-hover": "color-mix(in srgb, var(--foreground) 4%, transparent)", "primitive-surface-active": "color-mix(in srgb, var(--foreground) 7%, transparent)", "primitive-surface-selected": "color-mix(in srgb, var(--foreground) 6%, transparent)", "primitive-border-subtle": "color-mix(in srgb, var(--border) 60%, transparent)", "primitive-ring": "color-mix(in srgb, var(--foreground) 45%, transparent)", "primitive-text-secondary": "color-mix(in srgb, var(--foreground) 72%, transparent)", "primitive-text-placeholder": "color-mix(in srgb, var(--muted-foreground) 85%, transparent)", "primitive-radius": "0.875rem", "primitive-font-sans": "var(--font-sans)", "primitive-font-display": "var(--font-display)", "primitive-font-mono": "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace", "primitive-control-solid": "#24292d", "primitive-control-solid-hover": "#2c3237", "primitive-control-solid-active": "#1e2326", "primitive-control-solid-foreground": "#ffffff", "primitive-chart-1": "#2a78d6", "primitive-chart-2": "#1baf7a", "primitive-chart-3": "#eda100", "primitive-chart-4": "#008300", "primitive-chart-5": "#4a3aa7", "primitive-chart-6": "#e34948", "primitive-text-inverse": "#ffffff", "primitive-text-hint": "0.6875rem", "primitive-destructive": "#dc2626", "primitive-destructive-hover": "#b91c1c", "primitive-destructive-active": "#991b1b", "primitive-destructive-foreground": "#ffffff", "primitive-destructive-surface": "rgba(220, 38, 38, 0.10)", "primitive-destructive-border": "rgba(220, 38, 38, 0.45)", "primitive-success": "#047857", "primitive-success-surface": "rgba(4, 120, 87, 0.10)", "primitive-success-border": "rgba(4, 120, 87, 0.45)", "primitive-warning": "#b45309", "primitive-warning-surface": "rgba(180, 83, 9, 0.10)", "primitive-warning-border": "rgba(180, 83, 9, 0.45)", "primitive-info": "#0369a1", "primitive-info-surface": "rgba(3, 105, 161, 0.10)", "primitive-info-border": "rgba(3, 105, 161, 0.45)", "primitive-radius-control": "var(--primitive-radius)", "primitive-radius-control-sm": "max(0px, calc(var(--primitive-radius) - 4px))", "primitive-radius-surface": "calc(var(--primitive-radius) + min(2px, var(--primitive-radius)))", "primitive-radius-item": "max(0px, calc(var(--primitive-radius-surface) - 6px))", "primitive-control-height-sm": "2rem", "primitive-control-height-md": "2.25rem", "primitive-control-height-lg": "2.5rem", "primitive-shadow-raised": "0 1px 2px rgba(0, 0, 0, 0.06), 0 8px 24px -12px rgba(0, 0, 0, 0.08)", "primitive-shadow-overlay": "0 1px 2px rgba(0, 0, 0, 0.06), 0 18px 48px -24px rgba(0, 0, 0, 0.35)", "primitive-shadow-modal": "0 1px 2px rgba(0, 0, 0, 0.08), 0 24px 64px -28px rgba(0, 0, 0, 0.42)", "primitive-z-overlay": "100", "primitive-z-popover": "130", "primitive-z-toast": "140", "primitive-backdrop": "rgba(0, 0, 0, 0.6)", "primitive-ease": "cubic-bezier(0.23, 1, 0.32, 1)" }, "dark": { "background": "#0a0a0b", "foreground": "#f5f5f6", "surface": "#111113", "surface-muted": "#18181b", "border": "rgba(255, 255, 255, 0.08)", "border-strong": "rgba(255, 255, 255, 0.14)", "muted": "#1c1c1f", "muted-foreground": "#a1a1aa", "ring": "#cd1c18", "pattern-fg": "rgba(255, 255, 255, 0.06)", "llb-primary": "#f5f5f6", "llb-primary-fg": "#0a0a0b", "llb-success": "#2da44e", "llb-error": "#f85149", "primitive-surface-elevated": "#0f0f10", "primitive-surface-overlay": "#141415", "primitive-surface-hover": "color-mix(in srgb, var(--foreground) 5%, transparent)", "primitive-surface-active": "color-mix(in srgb, var(--foreground) 10%, transparent)", "primitive-surface-selected": "color-mix(in srgb, var(--foreground) 8%, transparent)", "primitive-border-subtle": "color-mix(in srgb, var(--border) 60%, transparent)", "primitive-ring": "color-mix(in srgb, var(--foreground) 45%, transparent)", "primitive-text-secondary": "color-mix(in srgb, var(--foreground) 72%, transparent)", "primitive-text-placeholder": "color-mix(in srgb, var(--muted-foreground) 85%, transparent)", "primitive-radius": "0.875rem", "primitive-font-sans": "var(--font-sans)", "primitive-font-display": "var(--font-display)", "primitive-font-mono": "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace", "primitive-control-solid": "#f5f3ee", "primitive-control-solid-hover": "#ffffff", "primitive-control-solid-active": "#e8e4da", "primitive-control-solid-foreground": "#151719", "primitive-chart-1": "#3987e5", "primitive-chart-2": "#199e70", "primitive-chart-3": "#c98500", "primitive-chart-4": "#008300", "primitive-chart-5": "#9085e9", "primitive-chart-6": "#e66767", "primitive-text-inverse": "#151719", "primitive-text-hint": "0.6875rem", "primitive-destructive": "#e11d2e", "primitive-destructive-hover": "#c1121f", "primitive-destructive-active": "#a4161a", "primitive-destructive-foreground": "#ffffff", "primitive-destructive-surface": "rgba(225, 29, 46, 0.12)", "primitive-destructive-border": "rgba(225, 29, 46, 0.52)", "primitive-success": "#34d399", "primitive-success-surface": "rgba(52, 211, 153, 0.12)", "primitive-success-border": "rgba(52, 211, 153, 0.45)", "primitive-warning": "#fbbf24", "primitive-warning-surface": "rgba(251, 191, 36, 0.12)", "primitive-warning-border": "rgba(251, 191, 36, 0.45)", "primitive-info": "#38bdf8", "primitive-info-surface": "rgba(56, 189, 248, 0.12)", "primitive-info-border": "rgba(56, 189, 248, 0.45)", "primitive-radius-control": "var(--primitive-radius)", "primitive-radius-control-sm": "max(0px, calc(var(--primitive-radius) - 4px))", "primitive-radius-surface": "calc(var(--primitive-radius) + min(2px, var(--primitive-radius)))", "primitive-radius-item": "max(0px, calc(var(--primitive-radius-surface) - 6px))", "primitive-control-height-sm": "2rem", "primitive-control-height-md": "2.25rem", "primitive-control-height-lg": "2.5rem", "primitive-shadow-raised": "0 1px 2px rgba(0, 0, 0, 0.4), 0 8px 24px -12px rgba(0, 0, 0, 0.6)", "primitive-shadow-overlay": "0 1px 2px rgba(0, 0, 0, 0.4), 0 20px 56px -28px rgba(0, 0, 0, 0.72)", "primitive-shadow-modal": "0 1px 2px rgba(0, 0, 0, 0.45), 0 28px 72px -32px rgba(0, 0, 0, 0.8)", "primitive-z-overlay": "100", "primitive-z-popover": "130", "primitive-z-toast": "140", "primitive-backdrop": "rgba(0, 0, 0, 0.6)", "primitive-ease": "cubic-bezier(0.23, 1, 0.32, 1)" } }, "css": { "@layer base": { ":where([data-wensity-primitive])": { "font-family": "var(--primitive-font-sans)" } }, "@utility scrollbar-hidden": { "scrollbar-width": "none", "-ms-overflow-style": "none", "&::-webkit-scrollbar": { "display": "none" } }, "@keyframes wensity-marquee-x": { "from": { "transform": "translate3d(0, 0, 0)" }, "to": { "transform": "translate3d(-50%, 0, 0)" } }, "@keyframes wensity-marquee-x-reverse": { "from": { "transform": "translate3d(-50%, 0, 0)" }, "to": { "transform": "translate3d(0, 0, 0)" } }, "@keyframes wensity-morph-rot-cw": { "from": { "transform": "rotate(0deg)" }, "to": { "transform": "rotate(360deg)" } }, "@keyframes wensity-morph-rot-ccw": { "from": { "transform": "rotate(0deg)" }, "to": { "transform": "rotate(-360deg)" } } }, "docs": "Free Wensity component. Installs to @components/wensity/input-otp.tsx. For Pro components and updates, use pnpm dlx wensity@latest add input-otp.", "categories": [ "UI Primitives", "wensity", "free" ], "meta": { "wensity": { "slug": "input-otp", "access": "free", "category": "UI Primitives", "kind": "component", "componentUrl": "https://ui.wensity.com/primitives/input-otp", "shadcnUrl": "https://ui.wensity.com/r/input-otp", "cliInstall": "pnpm dlx wensity@latest add input-otp" } } }