# OTP input

OTP input is a neobrutalist React component from Cruise UI. 4 or 6 boxes for a one-time code, with paste, error and a resend countdown.

Install (with the @cruiseui registry in components.json, see Installation):

```bash
npx shadcn@latest add @cruiseui/otp-input
```

Import: `import { CruiseOtpInput } from "@/components/cruise/otp-input";`

## When to use

A code sent by SMS or email to sign in or confirm a payment.

## When not to

Passwords or PINs people chose themselves (use an Input).

## Example (React)

```tsx
"use client";

import { useState } from "react";
import { CruiseOtpInput } from "@/components/cruise/otp-input";

export default function OtpInputExample() {
  const [error, setError] = useState<string>();
  return (
    <CruiseOtpInput
      label="Enter the 6-digit code we sent to 0803 *** 4567"
      error={error}
      onComplete={(code) => setError(code === "123456" ? undefined : "That code didn't work. Check it and try again.")}
      onResend={() => setError(undefined)}
    />
  );
}
```

## HTML

Needs theme.css (https://cruiseui.dev/theme.css).

```html
<p id="otp-label">Enter the 6-digit code we sent to 0803 *** 4567</p>
<div class="otp" role="group" aria-labelledby="otp-label" data-otp>
  <input class="otp-box" inputmode="numeric" autocomplete="one-time-code" aria-label="Digit 1 of 6">
  <input class="otp-box" inputmode="numeric" autocomplete="off" aria-label="Digit 2 of 6">
  <input class="otp-box" inputmode="numeric" autocomplete="off" aria-label="Digit 3 of 6">
  <input class="otp-box" inputmode="numeric" autocomplete="off" aria-label="Digit 4 of 6">
  <input class="otp-box" inputmode="numeric" autocomplete="off" aria-label="Digit 5 of 6">
  <input class="otp-box" inputmode="numeric" autocomplete="off" aria-label="Digit 6 of 6">
</div>
<p class="otp-error" role="alert" hidden>
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" aria-hidden="true"><circle cx="12" cy="12" r="10"/><path d="M12 8v4M12 16h.01"/></svg>
  That code didn't work. Check it and try again.
</p>
<button type="button" class="otp-resend" disabled>Resend code in 0:30</button>
```

## Props: CruiseOtpInput

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `length?` | `4 \| 6` | `6` | — |
| `label` | `string` | — | The instruction above the boxes; it labels the group. |
| `onComplete?` | `((code: string) => void)` | — | — |
| `error?` | `string` | — | Shown below the boxes with an icon; turns the borders --danger. |
| `resendAfter?` | `number` | `30` | Seconds before "Resend code" is enabled; omit onResend to hide it. |
| `onResend?` | `(() => void)` | — | — |
| `className?` | `string` | — | — |

## Accessibility

- inputmode="numeric" and autocomplete="one-time-code" on the first box, so phones offer the SMS code.
- Each box is labelled "Digit 1 of 6"…; errors are announced with role="alert".
