One-time-password builder for short verification codes. Use its headless controller to render individual character cells.
length()fixes the exact code length;controller.otpLengthtells your UI how many cells to showdigitsOnly(),lettersOnly()andalphanumeric()restrict the accepted character set; your UI chooses the matching keyboard hintmask()hides the typed value behind a display character (e.g.•) while keeping the real value in form stategroups()defines the value length; pass the same visual grouping to your application-owned component- Manage cell refs in your UI to move focus forward after input, backward on Backspace, and distribute pasted codes
- Combine with
validateOn: 'onChange'at hook level for instant validation as the user types
Defaults, inheritance & field methods
- defaultValue is
'' - type is
otp - Shared methods: see Base field builder
OTP-specific methods:
| Method | Type | Description |
|---|---|---|
length(length, message?) | length: number | Fixes the exact expected code length and syncs the descriptor min/max values. |
digitsOnly(message?) | message?: string | Rejects non-digit characters and hints a numeric keyboard. |
lettersOnly(message?) | message?: string | Restricts the value to ASCII letters (A-Z, a-z). Disallowed keystrokes are dropped before reaching form state. |
alphanumeric(message?) | message?: string | Allows letters and digits (A-Z, a-z, 0-9). Useful for invitation codes mixing both. |
mask(char?) | char?: string (default '•') | Configures the masking character your application-owned OTP component should display while the real value stays in form state. |
groups(sizes, separator?) | sizes: number[], separator?: string (default '-') | Configures logical groups (e.g. [3, 2] for ___-__). The total length becomes the sum of the sizes; your UI renders the separator. |
Recipes
Patterns that showcase otp-specific strengths.
Standard 6-digit verification
Verification6.tsxtsx
| 1 | const schema = { |
| 2 | code: field.otp('Verification code') |
| 3 | .length(6) |
| 4 | .digitsOnly() |
| 5 | .required(), |
| 6 | } |
Short 4-digit PIN
Pin4.tsxtsx
| 1 | const schema = { |
| 2 | pin: field.otp('PIN') |
| 3 | .length(4) |
| 4 | .digitsOnly('Digits only') |
| 5 | .required(), |
| 6 | } |
Alphanumeric invitation code
InviteCode.tsxtsx
| 1 | const schema = { |
| 2 | inviteCode: field.otp('Invitation code') |
| 3 | .length(6) |
| 4 | .alphanumeric() |
| 5 | .required(), |
| 6 | } |
Letters-only access code
AccessCode.tsxtsx
| 1 | const schema = { |
| 2 | accessCode: field.otp('Access code') |
| 3 | .length(4) |
| 4 | .lettersOnly() |
| 5 | .required(), |
| 6 | } |
Masked verification code
MaskedOtp.tsxtsx
| 1 | const schema = { |
| 2 | code: field.otp('Verification code') |
| 3 | .length(6) |
| 4 | .digitsOnly() |
| 5 | .mask() |
| 6 | .required(), |
| 7 | } |
Grouped layout with a separator
GroupedOtp.tsxtsx
| 1 | const schema = { |
| 2 | code: field.otp('Verification code') |
| 3 | .groups([3, 2], '-') |
| 4 | .digitsOnly() |
| 5 | .required(), |
| 6 | } |
Auto-submit when the code is full
AutoSubmitOtp.tsxtsx
| 1 | const schema = { |
| 2 | code: field.otp('Verification code').length(6).digitsOnly().required(), |
| 3 | } |
| 4 | |
| 5 | const form = useFormBridge(schema, { |
| 6 | validateOn: 'onChange', |
| 7 | }) |
| 8 | const { Form, fieldController, state, submit } = form |
| 9 | |
| 10 | useEffect(() => { |
| 11 | if (state.values.code?.length === 6 && state.isValid) { |
| 12 | submit() |
| 13 | } |
| 14 | }, [state.values.code, state.isValid, submit]) |
| 15 | |
| 16 | <Form onSubmit={verifyCode}> |
| 17 | <OtpField form={form} name="code" /> |
| 18 | </Form> |