# OTP Input

> A React OTP input for one-time verification codes and PINs, with digits that roll into place and a caret that slides between slots.

- Type: React component (Inputs)
- Access: Free (the source is open to everyone)
- Version: 1.0.1
- Install: `npx wingo-ui@latest add otp-input`
- Docs: https://wingo-ui.com/components/otp-input

## Interaction

Type to fill each slot and move to the next one. Backspace clears a slot in place, then steps back on the next press. Arrow keys move between slots, and a caret slides along with you. Pasting a code, or letting the phone autofill one from a text message, drops it straight in. Set the status to turn the slots green, or shake them red on a wrong code.

## Installation

```bash
npx wingo-ui@latest add otp-input
```

The CLI adds the files, their dependencies and a version record for updates.

With shadcn: `npx shadcn@latest add https://wingo-ui.com/r/otp-input.json`

Files:

- `components/ui/otp-input.tsx`

npm dependencies: `npm i motion`

Wingo UI dependencies (installed automatically):

- [UIProvider](https://wingo-ui.com/components/ui-config) ([Markdown](https://wingo-ui.com/components/ui-config.md))

## Usage

```tsx
import { useState } from "react"
import OtpInput, { type OtpStatus } from "@/components/ui/otp-input"

export function Demo() {
  const [status, setStatus] = useState<OtpStatus>("idle")

  return (
    <OtpInput
      length={6}
      size="md"
      status={status}
      onChange={() => setStatus("idle")}
      onComplete={(code) => setStatus(checkCode(code) ? "success" : "error")}
    />
  )
}
```

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `length?` | `number` | `6` | How many boxes to render, so a 4 digit code is length={4}. Any count works. |
| `size?` | `"sm" \| "md" \| "lg"` | `"md"` | Overall scale of the boxes. Maps to 40px (sm, 44px on touch screens), 48px (md), and 56px (lg), and carries the text, caret, and gaps with it. In a container narrower than the row, the boxes shrink evenly and stay square, so 8 slots still fit a 360px phone. |
| `type?` | `"numbers" \| "letters" \| "both"` | `"numbers"` | Which characters a slot accepts. Anything else is ignored, including on paste. |
| `status?` | `"idle" \| "success" \| "error"` | `"idle"` | Drives the feedback state. Success traces a green ring around each box in turn, error rings them red and shakes the row once. |
| `radius?` | `"none" \| "sm" \| "md" \| "lg" \| "full"` | `"md"` | Slot corners as a share of the slot, so they shrink with it and match the success ring: square, half, the original 20 / 25 / 29% per size, rounder, or circles. |
| `surface?` | `string` | `""` | Any CSS color for the slot fill. Empty uses #F4F4F9 light, #262626 dark. |
| `textColor?` | `string` | `""` | Any CSS color for the characters and the caret. Empty is black on light pages, white on dark ones. |
| `successColor?` | `string` | `"#34C759"` | Any CSS color for the ring drawn around each slot on success. |
| `errorColor?` | `string` | `"#FF3B30"` | Any CSS color for the ring shown on error. |
| `mask?` | `boolean` | `false` | Hides the characters, like a password field. |
| `disabled?` | `boolean` | `false` | Blocks input and dims every slot. |
| `autoFocus?` | `boolean` | `false` | Focuses the first slot on mount. |
| `slotLabel?` | `string` | `""` | Accessible name template for each slot; {index} and {length} fill in, e.g. "Cifra {index} din {length}". Empty reads "Digit 1 of 6" (or "Character" for letters). |
| `value?` | `string` |  | The current code. Pass it to control the input yourself; leave it out to let the component track its own state. |
| `defaultValue?` | `string` | `""` | Starting code when the input is uncontrolled. |
| `onChange?` | `(value: string) => void` |  | Fires on every edit with the full code so far. |
| `onComplete?` | `(value: string) => void` |  | Fires once the last slot is filled. |
| `className?` | `string` |  | Extra classes for the row that wraps the slots. |
| `slotClassName?` | `string` |  | Extra classes for each slot, for sizing and colors. |

## Examples

- **Verify a phone number**: A sign-in step with a controlled value, a Verify button, a resend link and a live message.
- **Sizes**: sm, md and lg. On a narrow screen the slots shrink evenly and stay square.
- **Success, error and disabled**: status rings each slot green or red; error also shakes the row once.
- **Masked PIN**: mask hides the digits, radius full makes round slots, slotLabel names each one for screen readers.
- **Backup code**: type both accepts letters and digits, here in eight small slots.

## See also

- [Masked Input](https://wingo-ui.com/components/masked-input) ([Markdown](https://wingo-ui.com/components/masked-input.md))
- [Password Input](https://wingo-ui.com/components/password-input) ([Markdown](https://wingo-ui.com/components/password-input.md))
- [Input](https://wingo-ui.com/components/input) ([Markdown](https://wingo-ui.com/components/input.md))
- [Field](https://wingo-ui.com/components/field) ([Markdown](https://wingo-ui.com/components/field.md))
- [Login Page](https://wingo-ui.com/components/login-page) ([Markdown](https://wingo-ui.com/components/login-page.md))

---

Canonical: https://wingo-ui.com/components/otp-input
More components: https://wingo-ui.com/components · Index for agents: https://wingo-ui.com/llms.txt
