OtpInput
A one-time-code field: one hidden input under drawn cells, with feedback for verifying and wrong codes.
import OtpInput from "swshd/OtpInput";OtpInput draws a one-time code over one text field. The field owns the value and autofill; the cells provide the visual rhythm. The caller controls verification state and handles success by navigating away.
The default is six compact cells. Choose four digits or stretch them with fullWidth. While verifying, edits are locked and the cells breathe. Entering invalid plays danger ink, a shake, and a right-to-left drain before the uncontrolled code clears.
Choose whether any six digits should be accepted or rejected, then complete the code.
Four- and six-digit layouts, full-width cells, disabled, and verifying.
The active theme paints the cells, digits, caret, and invalid digit from OTP_INPUT_TOKENS: cell fill fill.raised.default; digit ink text.primary; caret ink text.primary; invalid digit status.danger. Ink changes instantly; motion affects only transforms and opacity.
- Digits rise 0.45 of their line height from scale 0.86 over 260 ms (
motion.duration.slow) withmotion.easing.out(cubic-bezier(0.23, 1, 0.32, 1)). New digits stagger by 45 ms. - A landing cell swells to 1.04 over 260 ms in two 130 ms halves with
motion.easing.inOut(cubic-bezier(0.65, 0, 0.35, 1)). - The caret spring uses stiffness 720, damping 29, and mass 1. It stretches 0.015 per px/s up to 5; it blinks every 1100 ms down to opacity 0.25. Show/hide fades over 120 ms.
- Verifying sends a 0.07-deep breath wave through the cells every 900 ms.
- Reject shakes for 620 ms at amplitude 5, decaying over 190 ms at 10 Hz with phase step 0.55. 300 ms in, while the shake is still settling, digits start draining right to left, staggered by 45 ms, each over 220 ms and 8 px. The field clears and unlocks after 620 ms + 45 ms per digit + 160 ms.
- Reduced motion fades digits in over 120 ms, keeps the reject timeline with an opacity-only drain, and drops the pop, breath, shake, translation, caret spring, stretch, and blink.
import OtpInput from "swshd/OtpInput";
<OtpInput
value={code}
onChangeText={setCode}
onComplete={verify}
status={status}
accessibilityLabel={t("auth.verificationCode")}
/>The hidden text field is the single accessible control and carries the code value, translated name, disabled/busy state, and invalid state. Drawn cells, digits, and caret are hidden from assistive technology.
The numeric one-time-code field exposes SMS autofill. On web, arrow keys and Home/End keep the insertion point at the end, and clicking inside the field restores end selection. Provide a translated accessibilityLabel; do not use the digits as the name.
| Prop | Type | Default | Description |
|---|---|---|---|
length | 4 | 6 | 6 | Number of digits in the code. |
value | string | — | Controlled digits. Non-digits are stripped and the result truncated before display. |
defaultValue | string | — | Uncontrolled initial digits. |
onChangeText | (code: string) => void | — | Sanitized digits after each accepted edit, and an empty string when rejection finishes. |
onComplete | (code: string) => void | — | Fires when an accepted edit fills the final cell. |
status | "idle" | "verifying" | "invalid" | "idle" | Caller-driven verification state. Success is handled by caller navigation. |
accessibilityLabel | string | — | Translated accessible name for the one-time-code field. |
fullWidth | boolean | false | Stretch the cells across the parent's width. |
disabled | boolean | false | Disables the field, dims cells, and hides the caret. |
autoFocus | boolean | false | Focus the field on mount. |
testID | string | — | Test identifier for the hidden text field. |
ref | Ref<OtpInputHandle> | — | Imperative focus(), blur(), and clear() methods; clear only resets uncontrolled state. |