input otp
Accessible one-time password component with paste-to-fill support.
Philosophy
OTP inputs are deceptively complex: they need to handle paste, auto-fill, focus management across segments, and mobile keyboard optimization. We isolate this complexity in a dedicated component rather than stretching Input to cover it. Each slot is its own element, giving you full control over styling and animation per digit.
How It's Built
Installation
npx @gentleduck/cli add input-otp
npx @gentleduck/cli add input-otp
Usage
import {
InputOTP,
InputOTPGroup,
InputOTPSeparator,
InputOTPSlot,
} from "@/components/ui/input-otp"import {
InputOTP,
InputOTPGroup,
InputOTPSeparator,
InputOTPSlot,
} from "@/components/ui/input-otp"<InputOTP maxLength={6}>
<InputOTPGroup>
<InputOTPSlot />
<InputOTPSlot />
<InputOTPSlot />
</InputOTPGroup>
<InputOTPSeparator />
<InputOTPGroup>
<InputOTPSlot />
<InputOTPSlot />
<InputOTPSlot />
</InputOTPGroup>
</InputOTP><InputOTP maxLength={6}>
<InputOTPGroup>
<InputOTPSlot />
<InputOTPSlot />
<InputOTPSlot />
</InputOTPGroup>
<InputOTPSeparator />
<InputOTPGroup>
<InputOTPSlot />
<InputOTPSlot />
<InputOTPSlot />
</InputOTPGroup>
</InputOTP>Examples
Pattern
Use the pattern prop to define a custom pattern for the OTP input.
...
<InputOTP maxLength={6}>
<InputOTPGroup>
<InputOTPSlot />
{/* ... */}
</InputOTPGroup>
</InputOTP>
...
<InputOTP maxLength={6}>
<InputOTPGroup>
<InputOTPSlot />
{/* ... */}
</InputOTPGroup>
</InputOTP>Separator
You can use the <InputOTPSeparator /> component to add a separator between the input groups.
import {
InputOTP,
InputOTPGroup,
InputOTPSeparator,
InputOTPSlot,
} from "@/components/ui/input-otp"
...
<InputOTP maxLength={4}>
<InputOTPGroup>
<InputOTPSlot />
<InputOTPSlot />
</InputOTPGroup>
<InputOTPSeparator />
<InputOTPGroup>
<InputOTPSlot />
<InputOTPSlot />
</InputOTPGroup>
</InputOTP>import {
InputOTP,
InputOTPGroup,
InputOTPSeparator,
InputOTPSlot,
} from "@/components/ui/input-otp"
...
<InputOTP maxLength={4}>
<InputOTPGroup>
<InputOTPSlot />
<InputOTPSlot />
</InputOTPGroup>
<InputOTPSeparator />
<InputOTPGroup>
<InputOTPSlot />
<InputOTPSlot />
</InputOTPGroup>
</InputOTP>Controlled
You can use the value and onValueChange props to control the input value.
Custom separator
Form
Paste
- Pasting fills slots starting at the focused input.
- Characters are filtered by the
patternprop. onValueChangefires after paste and per-key entry.
RTL Support
Direction is resolved through the shared primitives direction module. Use a local dir="rtl" override when the component exposes it, or set DirectionProvider at app/root level for global RTL/LTR behavior.
Motion
Motion components work standalone, but some compositions may behave unexpectedly — this is still under active development. If you find a broken composition, please file an issue.
Use MotionInputOTP for a scale+blur entrance animation on the entire OTP input powered by motion.
Requires the motion package. Use MotionInputOTP instead of InputOTP. All other sub-components stay the same.
API Reference
InputOTP
The root component that provides OTP context and manages slot focus, paste, and value state.
| Prop | Type | Default | Description |
|---|---|---|---|
className | string | - | Additional class names for the wrapper div |
children | React.ReactNode | - | OTP groups, slots, and separators |
value | string | - | Controlled value of the OTP input |
onValueChange | (value: string) => void | - | Callback fired when the combined slot value changes |
pattern | RegExp | /^.$/ | Regex used to validate each entered character |
maxLength | number | - | Optional cap for active slots used by keyboard and paste behavior |
name | string | - | Optional field name (useful with form libraries) |
dir | 'ltr' | 'rtl' | inherited | Local direction override |
ref | React.Ref<HTMLDivElement> | - | Ref forwarded to the wrapper div |
aria-label | string | "otp-one-time-password" | Accessible label for the OTP region |
...props | Omit<React.HTMLProps<HTMLDivElement>, 'pattern'> | - | Additional props to spread to the content div |
InputOTPGroup
Groups related OTP slots together with flex layout.
| Prop | Type | Default | Description |
|---|---|---|---|
className | string | - | Additional class names for the group div |
children | React.ReactNode | - | OTP slots to group together |
ref | React.Ref<HTMLDivElement> | - | Ref forwarded to the group div |
...props | React.HTMLProps<HTMLDivElement> | - | Additional props to spread to the content div |
InputOTPSlot
An individual input element representing a single OTP digit.
| Prop | Type | Default | Description |
|---|---|---|---|
className | string | - | Additional class names for the input element |
ref | React.Ref<HTMLInputElement> | - | Ref forwarded to the input element |
...props | React.HTMLProps<HTMLInputElement> | - | Additional props to spread to the input element |
InputOTPSeparator
A visual separator rendered between OTP groups.
| Prop | Type | Default | Description |
|---|---|---|---|
className | string | - | Additional class names for the separator div |
customIndicator | React.ReactNode | <Dot /> | Custom element to render instead of the default dot icon |
ref | React.Ref<HTMLDivElement> | - | Ref forwarded to the separator div |
...props | React.HTMLProps<HTMLDivElement> | - | Additional props to spread to the content div |
MotionInputOTP
Same props as InputOTP. Adds scale+blur entrance animation on the entire OTP container. Requires the motion package.