OTP Field
A one-time password input split into individual character slots with automatic focus management.Anatomy
Import and assemble the component. length is required so the field can size, validate, and detect completion before all slots hydrate.
1import { OTPField } from "@raystack/apsara";23<OTPField length={6}>4 <OTPField.Input />5 <OTPField.Input />6 <OTPField.Input />7 <OTPField.Separator />8 <OTPField.Input />9 <OTPField.Input />10 <OTPField.Input />11</OTPField>
API Reference
Root
Groups all parts of the field and manages their state.
Prop
Type
Input
An individual character slot. Render one per slot (typically using Array.from).
Prop
Type
Separator
A visual separator between slot groups, styled to fit between OTP slots.
Prop
Type
Examples
With separator
Group slots visually with OTPField.Separator to make long codes easier to read.
1<OTPField length={6}>2 {Array.from({ length: 3 }, (_, i) => (3 <OTPField.Input key={i} aria-label={`Character ${i + 1} of 6`} />4 ))}5 <OTPField.Separator />6 {Array.from({ length: 3 }, (_, i) => (7 <OTPField.Input key={i + 3} aria-label={`Character ${i + 4} of 6`} />8 ))}9</OTPField>
Masked
Use mask to obscure entered characters — useful for sensitive codes.
1<OTPField length={6} mask>2 {Array.from({ length: 6 }, (_, i) => (3 <OTPField.Input key={i} aria-label={`Character ${i + 1} of 6`} />4 ))}5</OTPField>
Alphanumeric
Use validationType to accept letters, digits, or both. Defaults to "numeric".
1<OTPField length={6} validationType="alphanumeric">2 {Array.from({ length: 6 }, (_, i) => (3 <OTPField.Input key={i} aria-label={`Character ${i + 1} of 6`} />4 ))}5</OTPField>
Disabled
Set disabled to prevent interaction.
1<OTPField length={6} disabled defaultValue="123">2 {Array.from({ length: 6 }, (_, i) => (3 <OTPField.Input key={i} aria-label={`Character ${i + 1} of 6`} />4 ))}5</OTPField>
Read-only
Set readOnly to display a value without allowing edits.
1<OTPField length={6} readOnly defaultValue="934821">2 {Array.from({ length: 6 }, (_, i) => (3 <OTPField.Input key={i} aria-label={`Character ${i + 1} of 6`} />4 ))}5</OTPField>
Controlled
Pass value and onValueChange to control the field from React state.
1(function ControlledOTP() {2 const [value, setValue] = React.useState("");34 return (5 <Flex direction="column" gap={4} align="start">6 <OTPField length={6} value={value} onValueChange={setValue}>7 {Array.from({ length: 6 }, (_, i) => (8 <OTPField.Input key={i} aria-label={`Character ${i + 1} of 6`} />9 ))}10 </OTPField>11 <Text size="small">12 Current value: <code>{value || "(empty)"}</code>13 </Text>14 </Flex>15 );
Complete callback
onValueComplete fires once all slots are filled. Combine with autoSubmit to submit the surrounding form automatically.
1(function CompleteOTP() {2 const [submitted, setSubmitted] = React.useState("");34 return (5 <Flex direction="column" gap={4} align="start">6 <OTPField length={6} onValueComplete={(value) => setSubmitted(value)}>7 {Array.from({ length: 6 }, (_, i) => (8 <OTPField.Input key={i} aria-label={`Character ${i + 1} of 6`} />9 ))}10 </OTPField>11 <Text size="small">12 {submitted ? `Submitted: ${submitted}` : "Type all 6 digits to submit"}13 </Text>14 </Flex>15 );
Custom sanitization
Set validationType="none" and provide sanitizeValue to restrict input to a custom set of characters.
1<OTPField2 length={4}3 validationType="none"4 inputMode="numeric"5 sanitizeValue={(val) => val.replace(/[^0-3]/g, "")}6>7 {Array.from({ length: 4 }, (_, i) => (8 <OTPField.Input key={i} aria-label={`Character ${i + 1} of 4`} />9 ))}10</OTPField>
With Field
Compose with Field to get an associated label and description.
1<Flex justify="center">2 <Field3 label="Verification code"4 description="Enter the 6-digit code we sent to your device."5 >6 <OTPField length={6}>7 {Array.from({ length: 6 }, (_, i) => (8 <OTPField.Input key={i} aria-label={`Character ${i + 1} of 6`} />9 ))}10 </OTPField>11 </Field>12</Flex>
Accessibility
- Each slot must have an accessible name. Use a wrapping
<label>,Field.Label, or setaria-labelon every slot announcing its position (e.g.,"Character 1 of 6"). - The first slot inherits
id; subsequent slots derive their ids as{id}-2,{id}-3, and so on. - A hidden validation input is rendered with
autoComplete="one-time-code"so browsers can suggest codes received via SMS. - Typing a character advances focus to the next slot. Backspace clears the current slot and steps backwards. Arrow keys move between slots without changing the value.
- Paste fills slots starting at the focused position, honoring the configured
validationTypeandlength.