4-Digit OTP
Standard verification code input with automatic focus advance and hidden field sync
<fieldset class="nds-form-group nds-otp-group">
<legend><span class="nds-label">Verification Code</span></legend>
<div class="nds-otp">
<div class="nds-form-container nds-otp-container">
<div class="nds-form-control">
<input type="text" name="otp-digit-1" inputmode="numeric" maxlength="1" pattern="[0-9]" autocomplete="one-time-code">
</div>
</div>
<div class="nds-form-container nds-otp-container">
<div class="nds-form-control">
<input type="text" name="otp-digit-2" inputmode="numeric" maxlength="1" pattern="[0-9]">
</div>
</div>
<div class="nds-form-container nds-otp-container">
<div class="nds-form-control">
<input type="text" name="otp-digit-3" inputmode="numeric" maxlength="1" pattern="[0-9]">
</div>
</div>
<div class="nds-form-container nds-otp-container">
<div class="nds-form-control">
<input type="text" name="otp-digit-4" inputmode="numeric" maxlength="1" pattern="[0-9]">
</div>
</div>
</div>
<input type="hidden" class="nds-otp-value" name="otp">
</fieldset>
OTP with Separator
Use the separator element to visually group digits, commonly used for 6-digit verification codes split into two groups of three.
<fieldset class="nds-form-group nds-otp-group">
<legend><span class="nds-label">Verification Code</span></legend>
<div class="nds-otp">
<div class="nds-form-container nds-otp-container">
<div class="nds-form-control">
<input type="text" name="otp-digit-1" inputmode="numeric" maxlength="1" pattern="[0-9]" autocomplete="one-time-code">
</div>
</div>
<div class="nds-form-container nds-otp-container">
<div class="nds-form-control">
<input type="text" name="otp-digit-2" inputmode="numeric" maxlength="1" pattern="[0-9]">
</div>
</div>
<div class="nds-form-container nds-otp-container">
<div class="nds-form-control">
<input type="text" name="otp-digit-3" inputmode="numeric" maxlength="1" pattern="[0-9]">
</div>
</div>
<span class="nds-otp-separator"></span>
<div class="nds-form-container nds-otp-container">
<div class="nds-form-control">
<input type="text" name="otp-digit-4" inputmode="numeric" maxlength="1" pattern="[0-9]">
</div>
</div>
<div class="nds-form-container nds-otp-container">
<div class="nds-form-control">
<input type="text" name="otp-digit-5" inputmode="numeric" maxlength="1" pattern="[0-9]">
</div>
</div>
<div class="nds-form-container nds-otp-container">
<div class="nds-form-control">
<input type="text" name="otp-digit-6" inputmode="numeric" maxlength="1" pattern="[0-9]">
</div>
</div>
</div>
<input type="hidden" class="nds-otp-value" name="otp">
</fieldset>
Validation States
OTP groups support validation states through the standard form data-status attribute. Status is automatically cleared when the user starts typing.
<fieldset class="nds-form-group nds-otp-group" id="my-otp">
<legend><span class="nds-label">Enter code 1234</span></legend>
<div class="nds-otp">
<div class="nds-form-container nds-otp-container">
<div class="nds-form-control">
<input type="text" name="otp-digit-1" inputmode="numeric" maxlength="1" pattern="[0-9]" autocomplete="one-time-code">
</div>
</div>
<div class="nds-form-container nds-otp-container">
<div class="nds-form-control">
<input type="text" name="otp-digit-2" inputmode="numeric" maxlength="1" pattern="[0-9]">
</div>
</div>
<div class="nds-form-container nds-otp-container">
<div class="nds-form-control">
<input type="text" name="otp-digit-3" inputmode="numeric" maxlength="1" pattern="[0-9]">
</div>
</div>
<div class="nds-form-container nds-otp-container">
<div class="nds-form-control">
<input type="text" name="otp-digit-4" inputmode="numeric" maxlength="1" pattern="[0-9]">
</div>
</div>
</div>
<input type="hidden" class="nds-otp-value" name="otp">
<div class="nds-form-footer" data-feedback-target hidden></div>
</fieldset>
Built-in Features
What you get out of the box with zero configuration
Initializes on page load and detects dynamically added groups via MutationObserver. No manual setup needed.
Arrow keys move between inputs (RTL-aware). Backspace clears and moves back. Delete clears and moves forward. Auto-advances on digit entry.
Pasting a multi-digit string distributes digits across all inputs from the first position. Non-numeric characters are stripped automatically.
A hidden input with nds-otp-value class stays in sync with the concatenated value for form submission.
Fires nds:otpChange on any input change, nds:otpComplete when all digits are filled, and nds:otpClear on clear.
High-contrast mode thickens input borders. Reduced motion disables transitions. autocomplete="one-time-code" enables autofill on mobile.
Usage Guidelines
When and how to use OTP inputs effectively
When to Use
- Verification codes sent via SMS, email, or authenticator apps
- Two-factor authentication flows
- Use 4 digits for simple codes, 6 digits with a separator for longer codes
- Add
autocomplete="one-time-code"on the first input for mobile autofill - Listen for
nds:otpCompleteto auto-submit when all digits are entered - For general text input, use form fields instead