Components
Input OTP
One-time code entry on Base UI OTP Field: square 44px cells sharing 1px ink rules, Barlow Condensed figures.
Preview
Enter the 6 digits we sent to your device.
Install
bash
npx shadcn add @eloquent/input-otpUsage
tsx
import { Label } from "@/components/ui/label";
import { InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot } from "@/components/ui/input-otp";
<Label htmlFor="code">Verification code</Label>
<InputOTP id="code" length={6} slotLabel={(n, total) => `Digit ${n} of ${total}`}>
<InputOTPGroup>
{[0, 1, 2].map((i) => <InputOTPSlot key={i} index={i} />)}
</InputOTPGroup>
<InputOTPSeparator />
<InputOTPGroup>
{[3, 4, 5].map((i) => <InputOTPSlot key={i} index={i} />)}
</InputOTPGroup>
</InputOTP>slotLabel names slots 2 to n and is required, so no English is built in. Name the first slot with a <label htmlFor> that points at id, a FieldLabel, or aria-labelledby on InputOTP; Base UI ignores aria-label on the first slot. Use onValueComplete to react when every cell is filled, or autoSubmit to submit the form.
States
| State | Treatment |
|---|---|
| Empty, filled | 44px square cells sharing 1px ink rules; figures in Barlow Condensed 700 |
| Press | tint-1 fill. No hover effect |
| Focus | 2px ink outline, 2px offset, raised above the neighbouring cells |
| Invalid | 2px ink underline on each cell, with FieldError giving the ink icon and message |
| Disabled | Halftone text and border, cursor-not-allowed |
Keyboard
| Key | Action |
|---|---|
| Tab / Shift+Tab | Enter and leave the code field (one tab stop) |
Digits (or letters, per validationType) | Fill the cell and move to the next; other characters are rejected |
| Backspace | Clear the cell and step back |
| Arrow Left / Right | Move between cells |
| Paste | Fills every cell from the pasted code |
Vietnamese
Nhập 6 chữ số chúng tôi đã gửi tới thiết bị của bạn.
Ink adaptation
Upstream shadcn's rounded end cells, shadow, ring glow and blinking fake caret are removed. Built on Base UI's OTPField (stable in @base-ui/react 1.9.0), so there is no dependency on the input-otp package; each cell is a native input. Upstream's maxLength is length here, and slotLabel is new.