Glin UI
Primitive / Moleculebeta

Input OTP

A one-time-code input with segmented slots, paste and autofill support.

Edit on GitHub

Check your email

We sent a 6 digit code to maya@acme.com. It expires in 10 minutes.

Paste works too. The code fills every box.

Installation

Install from
pnpm dlx @glinui/cli@latest add input-otp

Prefer the package?

Usage

Variants

import { InputOTP, InputOTPGroup, InputOTPSlot } from "@glinui/ui"
export function Demo() {
return (
<div className="flex flex-col items-start gap-3">
<InputOTP maxLength={4} variant="glinr" defaultValue="12" aria-label="glinr">
<InputOTPGroup>{[0, 1, 2, 3].map((i) => <InputOTPSlot key={i} index={i} />)}</InputOTPGroup>
</InputOTP>
<InputOTP maxLength={4} variant="solid" defaultValue="12" aria-label="solid">
<InputOTPGroup>{[0, 1, 2, 3].map((i) => <InputOTPSlot key={i} index={i} />)}</InputOTPGroup>
</InputOTP>
<InputOTP maxLength={4} variant="plain" defaultValue="12" aria-label="plain">
<InputOTPGroup>{[0, 1, 2, 3].map((i) => <InputOTPSlot key={i} index={i} />)}</InputOTPGroup>
</InputOTP>
<InputOTP maxLength={4} variant="soft" defaultValue="12" aria-label="soft">
<InputOTPGroup>{[0, 1, 2, 3].map((i) => <InputOTPSlot key={i} index={i} />)}</InputOTPGroup>
</InputOTP>
<InputOTP maxLength={4} variant="outline" defaultValue="12" aria-label="outline">
<InputOTPGroup>{[0, 1, 2, 3].map((i) => <InputOTPSlot key={i} index={i} />)}</InputOTPGroup>
</InputOTP>
<InputOTP maxLength={4} variant="ghost" defaultValue="12" aria-label="ghost">
<InputOTPGroup>{[0, 1, 2, 3].map((i) => <InputOTPSlot key={i} index={i} />)}</InputOTPGroup>
</InputOTP>
</div>
)
}

Alphanumeric

import { InputOTP, InputOTPGroup, InputOTPSlot, REGEXP_ONLY_DIGITS_AND_CHARS } from "@glinui/ui"
export function Demo() {
return (
<InputOTP maxLength={4} pattern={REGEXP_ONLY_DIGITS_AND_CHARS} defaultValue="A1">
<InputOTPGroup>{[0, 1, 2, 3].map((i) => <InputOTPSlot key={i} index={i} />)}</InputOTPGroup>
</InputOTP>
)
}

Glass (opt-in)

Glass is opt-in and needs a backdrop behind it: pick Photo or Vivid in the stage backdrop switcher.

import { InputOTP, InputOTPGroup, InputOTPSlot } from "@glinui/ui"
export function Demo() {
return (
<InputOTP maxLength={4} variant="glass" defaultValue="12">
<InputOTPGroup>{[0, 1, 2, 3].map((i) => <InputOTPSlot key={i} index={i} />)}</InputOTPGroup>
</InputOTP>
)
}

Accessibility

  • A single real `input` carries focus, value and the accessible name, so screen readers announce one field, not N boxes.
  • Slots are `aria-hidden` visuals.
  • Paste and one-time-code autofill fill every slot at once.
  • Default `aria-label` is Verification code; override it.

Keyboard Interactions

KeyAction
0-9 / charactersFill the active slot and advance.
BackspaceDelete the previous character.
Arrow Left / RightMove the caret (mirrored in RTL).
Home / EndJump to first slot or end.
Ctrl/Cmd + VPaste a full code.

ARIA Attributes

  • `aria-label` on the input
  • `aria-invalid` highlights every slot
  • `inputmode="numeric"` for digit patterns

Reduced Motion

The blinking caret and colour transitions are disabled under prefers-reduced-motion.

Affected properties

caret opacity pulseborder-color

API Reference

InputOTP

PropTypeRequiredDefaultDescription
maxLengthnumberYes-Required. Number of characters.
value | defaultValuestring--Controlled or initial value.
onChange(value: string) => voidNo-Fires on every accepted change.
onComplete(value: string) => voidNo-Fires once when all slots are filled.
patternstringNoREGEXP_ONLY_DIGITSRegex source the whole value must match. Also exported: REGEXP_ONLY_CHARS, REGEXP_ONLY_DIGITS_AND_CHARS.
variantControlVariantPropNoambient (glinr)Slot look. Omit to follow the ambient design style. Glass is opt-in.
size"sm" | "md" | "lg"NomdSlot size.
autoCompletestring-one-time-codeEnables SMS and password manager autofill.
containerClassNamestringNo-Class for the outer container.
defaultValuestringNo""Initial value for uncontrolled usage.
valuestringNo-Controlled value.

InputOTPSlot

PropTypeRequiredDefaultDescription
indexnumber--Required. Zero-based position rendered by the slot.
containerClassNamestringNo-Class for the outer container.
defaultValuestringNo""Initial value for uncontrolled usage.
maxLengthnumberYes-Number of characters. Required.
onChange(value: string) => voidNo-Fires with the new value on every accepted change.
onComplete(value: string) => voidNo-Fires once when the value reaches maxLength.
patternstringNoREGEXP_ONLY_DIGITSRegular expression source the whole value must match, e.g. REGEXP_ONLY_DIGITS.
size"sm" | "md" | "lg"No"md"Variant option from inputOTPSlotVariants.
valuestringNo-Controlled value.
variantControlVariantPropNo-Surface look of the slots. Omit to follow the ambient design style. Same vocabulary as Input.

InputOTPGroup / InputOTPSeparator

PropTypeRequiredDefaultDescription
classNamestring--Layout grouping and visual divider.
containerClassNamestringNo-Class for the outer container.
defaultValuestringNo""Initial value for uncontrolled usage.
maxLengthnumberYes-Number of characters. Required.
onChange(value: string) => voidNo-Fires with the new value on every accepted change.
onComplete(value: string) => voidNo-Fires once when the value reaches maxLength.
patternstringNoREGEXP_ONLY_DIGITSRegular expression source the whole value must match, e.g. REGEXP_ONLY_DIGITS.
size"sm" | "md" | "lg"No"md"Variant option from inputOTPSlotVariants.
valuestringNo-Controlled value.
variantControlVariantPropNo-Surface look of the slots. Omit to follow the ambient design style. Same vocabulary as Input.

Source

Import directly from the package or browse the source on GitHub. Click any file to view it.

Import

import { InputOTP } from "@glinui/ui"

Source Files

Dependencies

@glinui/ui@phosphor-icons/reactclass-variance-authority

Generated API Snapshot

Beta

Auto-extracted from TypeScript source in packages/ui/src/components/input-otp.tsx. This section is in beta and may lag behind hand-curated docs. Regenerate with pnpm --filter @glinui/docs api:generate.

Generated: 2026-10-08T01:41:21.546Z · Full index: /docs/api-metadata

Primary Props Type

InputOTPProps

InputOTPProps

PropTypeRequiredDefaultDescription
containerClassNamestringNo-Class for the outer container.
defaultValuestringNo""Initial value for uncontrolled usage.
maxLengthnumberYes-Number of characters. Required.
onChange(value: string) => voidNo-Fires with the new value on every accepted change.
onComplete(value: string) => voidNo-Fires once when the value reaches maxLength.
patternstringNoREGEXP_ONLY_DIGITSRegular expression source the whole value must match, e.g. REGEXP_ONLY_DIGITS.
size"sm" | "md" | "lg"No"md"Variant option from inputOTPSlotVariants.
valuestringNo-Controlled value.
variantControlVariantPropNo-Surface look of the slots. Omit to follow the ambient design style. Same vocabulary as Input.

InputOTPSlotProps

PropTypeRequiredDefaultDescription
indexnumberYes-Zero-based position this slot renders.