> ## Documentation Index
> Fetch the complete documentation index at: https://araf.badr.co.id/llms.txt
> Use this file to discover all available pages before exploring further.

# Input OTP

> A one-time password input with individual digit slots — supports auto-focus progression, paste handling, and error states.

## Overview

<div style={{padding:"28px 24px",background:"#f9fafb",borderRadius:"12px",border:"1px solid #e5e7eb",display:"flex",flexDirection:"column",gap:"20px",alignItems:"center"}}>
  <div style={{display:"flex",gap:"8px",fontFamily:"Inter,sans-serif"}}>
    {["1","2","3","","",""].map((digit,i) => (
            <div key={i} style={{width:"44px",height:"52px",border:i===3?"2px solid #0479CE":"1px solid #D0D5DD",borderRadius:"8px",display:"flex",alignItems:"center",justifyContent:"center",fontSize:"20px",fontWeight:600,color:"#101828",background: i===3?"#F0F9FF":"#fff",boxShadow:i===3?"0 0 0 4px #CDE4F5":"none"}}>
              {digit}
              {i===3 && <span style={{width:"2px",height:"20px",background:"#0479CE",animation:"blink 1s step-end infinite",display:"inline-block"}}></span>}
            </div>
          ))}
  </div>
</div>

**Input OTP** provides individual digit slots for entering one-time passwords or verification codes. It handles auto-focus progression between slots, clipboard paste, and backspace deletion automatically.

***

## Installation

<CodeGroup>
  ```bash npm theme={null}
  npm install @araf-ds/core
  ```

  ```bash yarn theme={null}
  yarn add @araf-ds/core
  ```

  ```bash pnpm theme={null}
  pnpm add @araf-ds/core
  ```
</CodeGroup>

***

## Usage

```tsx theme={null}
import {
  InputOTP,
  InputOTPGroup,
  InputOTPSlot,
  InputOTPSeparator,
} from "@araf-ds/core"

export default function Example() {
  return (
    <InputOTP maxLength={6}>
      <InputOTPGroup>
        <InputOTPSlot index={0} />
        <InputOTPSlot index={1} />
        <InputOTPSlot index={2} />
      </InputOTPGroup>
      <InputOTPSeparator />
      <InputOTPGroup>
        <InputOTPSlot index={3} />
        <InputOTPSlot index={4} />
        <InputOTPSlot index={5} />
      </InputOTPGroup>
    </InputOTP>
  )
}
```

***

## Variants

### Basic (6 digits)

<div style={{padding:"24px",background:"#f9fafb",borderRadius:"12px",border:"1px solid #e5e7eb",display:"flex",justifyContent:"center"}}>
  <div style={{display:"flex",gap:"8px"}}>
    {[..."123456"].map((d,i) => (
            <div key={i} style={{width:"44px",height:"52px",border:"1px solid #D0D5DD",borderRadius:"8px",display:"flex",alignItems:"center",justifyContent:"center",fontSize:"20px",fontWeight:600,color:"#101828",background:"#fff",fontFamily:"Inter,sans-serif"}}>
              {d}
            </div>
          ))}
  </div>
</div>

```tsx theme={null}
<InputOTP maxLength={6}>
  <InputOTPGroup>
    <InputOTPSlot index={0} />
    <InputOTPSlot index={1} />
    <InputOTPSlot index={2} />
    <InputOTPSlot index={3} />
    <InputOTPSlot index={4} />
    <InputOTPSlot index={5} />
  </InputOTPGroup>
</InputOTP>
```

### With Separator (3 + 3)

<div style={{padding:"24px",background:"#f9fafb",borderRadius:"12px",border:"1px solid #e5e7eb",display:"flex",justifyContent:"center"}}>
  <div style={{display:"flex",alignItems:"center",gap:"8px",fontFamily:"Inter,sans-serif"}}>
    <div style={{display:"flex",gap:"6px"}}>
      {[..."485"].map((d,i) => (
                  <div key={i} style={{width:"44px",height:"52px",border:"1px solid #D0D5DD",borderRadius:"8px",display:"flex",alignItems:"center",justifyContent:"center",fontSize:"20px",fontWeight:600,color:"#101828",background:"#fff"}}>
                    {d}
                  </div>
                ))}
    </div>

    <span style={{color:"#D0D5DD",fontSize:"20px",fontWeight:300}}>—</span>

    <div style={{display:"flex",gap:"6px"}}>
      {[..."291"].map((d,i) => (
                  <div key={i} style={{width:"44px",height:"52px",border:"1px solid #D0D5DD",borderRadius:"8px",display:"flex",alignItems:"center",justifyContent:"center",fontSize:"20px",fontWeight:600,color:"#101828",background:"#fff"}}>
                    {d}
                  </div>
                ))}
    </div>
  </div>
</div>

```tsx theme={null}
<InputOTP maxLength={6}>
  <InputOTPGroup>
    <InputOTPSlot index={0} />
    <InputOTPSlot index={1} />
    <InputOTPSlot index={2} />
  </InputOTPGroup>
  <InputOTPSeparator />
  <InputOTPGroup>
    <InputOTPSlot index={3} />
    <InputOTPSlot index={4} />
    <InputOTPSlot index={5} />
  </InputOTPGroup>
</InputOTP>
```

### Error State

<div style={{padding:"24px",background:"#f9fafb",borderRadius:"12px",border:"1px solid #e5e7eb",display:"flex",flexDirection:"column",alignItems:"center",gap:"12px"}}>
  <div style={{display:"flex",gap:"8px"}}>
    {[..."999999"].map((d,i) => (
            <div key={i} style={{width:"44px",height:"52px",border:"1px solid #DC2626",borderRadius:"8px",display:"flex",alignItems:"center",justifyContent:"center",fontSize:"20px",fontWeight:600,color:"#DC2626",background:"#FEF3F2",fontFamily:"Inter,sans-serif"}}>
              {d}
            </div>
          ))}
  </div>

  <span style={{fontSize:"13px",color:"#DC2626",fontFamily:"Inter,sans-serif"}}>Invalid code. Please try again.</span>
</div>

```tsx theme={null}
<div>
  <InputOTP maxLength={6} state="error">
    <InputOTPGroup>
      <InputOTPSlot index={0} />
      <InputOTPSlot index={1} />
      <InputOTPSlot index={2} />
      <InputOTPSlot index={3} />
      <InputOTPSlot index={4} />
      <InputOTPSlot index={5} />
    </InputOTPGroup>
  </InputOTP>
  <p className="text-sm text-red-600 mt-2">Invalid code. Please try again.</p>
</div>
```

### Controlled with Pattern Validation

```tsx theme={null}
const [value, setValue] = useState("")

<InputOTP
  maxLength={6}
  value={value}
  onChange={setValue}
  pattern="^[0-9]+$"
>
  <InputOTPGroup>
    <InputOTPSlot index={0} />
    <InputOTPSlot index={1} />
    <InputOTPSlot index={2} />
    <InputOTPSlot index={3} />
    <InputOTPSlot index={4} />
    <InputOTPSlot index={5} />
  </InputOTPGroup>
</InputOTP>

{value.length === 6 && (
  <Button onClick={() => verifyOTP(value)}>Verify</Button>
)}
```

***

## API Reference

### InputOTP

<ParamField path="maxLength" type="number" required>
  Total number of digit slots.
</ParamField>

<ParamField path="value" type="string">
  Controlled OTP value string.
</ParamField>

<ParamField path="onChange" type="(value: string) => void">
  Callback fired on each character input.
</ParamField>

<ParamField path="pattern" type="string">
  Regex pattern to restrict allowed characters. Default: numeric only `^[0-9]+$`.
</ParamField>

<ParamField path="disabled" type="boolean" default="false">
  Disables all slots.
</ParamField>

<ParamField path="state" type="string" default="default">
  Validation state. Values: `default` · `error`
</ParamField>

<ParamField path="onComplete" type="(value: string) => void">
  Callback fired when all slots are filled. Useful for auto-submit.
</ParamField>

### InputOTPSlot

<ParamField path="index" type="number" required>
  Zero-based slot index (0 to `maxLength - 1`).
</ParamField>

***

## Accessibility

* Each slot renders as a single-character `<input>` with `inputmode="numeric"` and `autocomplete="one-time-code"`
* Focus automatically advances to the next slot on input and retreats on Backspace
* Pasting a full code fills all slots in one action
* The group container uses `role="group"` with an `aria-label` describing the purpose (e.g. "One-time password")
* Error state sets `aria-invalid="true"` on all slots

***

## Do's & Don'ts

<CardGroup cols={2}>
  <Card title="Do" icon="check" iconType="solid" color="#16A34A">
    * Use `onComplete` to trigger verification automatically when all slots are filled
    * Support clipboard paste — don't block `paste` events
    * Show a resend link with a cooldown timer below the input
    * Use 6 digits for standard OTP, 4 for simpler PIN inputs
  </Card>

  <Card title="Don't" icon="xmark" iconType="solid" color="#DC2626">
    * Don't use a regular `<Input>` for OTP — users expect the slot-by-slot pattern
    * Don't block numeric keyboard on mobile — use `inputmode="numeric"`
    * Don't auto-submit without giving the user a chance to correct mistakes
    * Don't hide the entered digits — OTP codes are not passwords
  </Card>
</CardGroup>
