Input OTP
A one-time-code input with segmented slots, paste and autofill support.
pnpm dlx @glinui/cli@latest add input-otpCheck 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
pnpm dlx @glinui/cli@latest add input-otpPrefer 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
| Key | Action |
|---|---|
| 0-9 / characters | Fill the active slot and advance. |
| Backspace | Delete the previous character. |
| Arrow Left / Right | Move the caret (mirrored in RTL). |
| Home / End | Jump to first slot or end. |
| Ctrl/Cmd + V | Paste 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-colorAPI Reference
InputOTP
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
maxLength | number | Yes | - | Required. Number of characters. |
value | defaultValue | string | - | - | Controlled or initial value. |
onChange | (value: string) => void | No | - | Fires on every accepted change. |
onComplete | (value: string) => void | No | - | Fires once when all slots are filled. |
pattern | string | No | REGEXP_ONLY_DIGITS | Regex source the whole value must match. Also exported: REGEXP_ONLY_CHARS, REGEXP_ONLY_DIGITS_AND_CHARS. |
variant | ControlVariantProp | No | ambient (glinr) | Slot look. Omit to follow the ambient design style. Glass is opt-in. |
size | "sm" | "md" | "lg" | No | md | Slot size. |
autoComplete | string | - | one-time-code | Enables SMS and password manager autofill. |
containerClassName | string | No | - | Class for the outer container. |
defaultValue | string | No | "" | Initial value for uncontrolled usage. |
value | string | No | - | Controlled value. |
InputOTPSlot
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
index | number | - | - | Required. Zero-based position rendered by the slot. |
containerClassName | string | No | - | Class for the outer container. |
defaultValue | string | No | "" | Initial value for uncontrolled usage. |
maxLength | number | Yes | - | Number of characters. Required. |
onChange | (value: string) => void | No | - | Fires with the new value on every accepted change. |
onComplete | (value: string) => void | No | - | Fires once when the value reaches maxLength. |
pattern | string | No | REGEXP_ONLY_DIGITS | Regular expression source the whole value must match, e.g. REGEXP_ONLY_DIGITS. |
size | "sm" | "md" | "lg" | No | "md" | Variant option from inputOTPSlotVariants. |
value | string | No | - | Controlled value. |
variant | ControlVariantProp | No | - | Surface look of the slots. Omit to follow the ambient design style. Same vocabulary as Input. |
InputOTPGroup / InputOTPSeparator
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
className | string | - | - | Layout grouping and visual divider. |
containerClassName | string | No | - | Class for the outer container. |
defaultValue | string | No | "" | Initial value for uncontrolled usage. |
maxLength | number | Yes | - | Number of characters. Required. |
onChange | (value: string) => void | No | - | Fires with the new value on every accepted change. |
onComplete | (value: string) => void | No | - | Fires once when the value reaches maxLength. |
pattern | string | No | REGEXP_ONLY_DIGITS | Regular expression source the whole value must match, e.g. REGEXP_ONLY_DIGITS. |
size | "sm" | "md" | "lg" | No | "md" | Variant option from inputOTPSlotVariants. |
value | string | No | - | Controlled value. |
variant | ControlVariantProp | No | - | 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-authorityGenerated API Snapshot
Beta
Generated API Snapshot
BetaAuto-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
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
containerClassName | string | No | - | Class for the outer container. |
defaultValue | string | No | "" | Initial value for uncontrolled usage. |
maxLength | number | Yes | - | Number of characters. Required. |
onChange | (value: string) => void | No | - | Fires with the new value on every accepted change. |
onComplete | (value: string) => void | No | - | Fires once when the value reaches maxLength. |
pattern | string | No | REGEXP_ONLY_DIGITS | Regular expression source the whole value must match, e.g. REGEXP_ONLY_DIGITS. |
size | "sm" | "md" | "lg" | No | "md" | Variant option from inputOTPSlotVariants. |
value | string | No | - | Controlled value. |
variant | ControlVariantProp | No | - | Surface look of the slots. Omit to follow the ambient design style. Same vocabulary as Input. |
InputOTPSlotProps
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
index | number | Yes | - | Zero-based position this slot renders. |