v1.6.x-dev NDS IQ v6

OTP Input - National Design System

One-time password input component for verification codes with automatic focus management, paste support, and RTL-aware keyboard navigation

4-Digit OTP

Standard verification code input with automatic focus advance and hidden field sync

Verification Code
<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" autofocus aria-label="Digit 1 of 4"> </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]" aria-label="Digit 2 of 4"> </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]" aria-label="Digit 3 of 4"> </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]" aria-label="Digit 4 of 4"> </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.

Verification Code
<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" aria-label="Digit 1 of 6"> </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]" aria-label="Digit 2 of 6"> </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]" aria-label="Digit 3 of 6"> </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]" aria-label="Digit 4 of 6"> </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]" aria-label="Digit 5 of 6"> </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]" aria-label="Digit 6 of 6"> </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.

Enter code 1234
<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" aria-label="Digit 1 of 4"> </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]" aria-label="Digit 2 of 4"> </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]" aria-label="Digit 3 of 4"> </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]" aria-label="Digit 4 of 4"> </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

Auto-initialization

Initializes on page load and detects dynamically added groups via MutationObserver. No manual setup needed.

Auto-focus

Opt-in via autofocus on any digit input. The component restores focus after init to recover from cases where the browser's native autofocus is lost to staggered component initialization.

Keyboard Navigation

Arrow keys move between inputs (RTL-aware). Backspace clears and moves back. Delete clears and moves forward. Auto-advances on digit entry.

Paste Support

Pasting a multi-digit string distributes digits across all inputs from the first position. Non-numeric characters are stripped automatically.

Hidden Field Sync

A hidden input with nds-otp-value class stays in sync with the concatenated value for form submission.

Custom Events

Fires nds:otpChange on any input change, nds:otpComplete when all digits are filled, and nds:otpClear on clear.

Accessibility

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:otpComplete to auto-submit when all digits are entered
  • For general text input, use form fields instead

Modifier Classes

ClassApplied toDescription
nds-sm.nds-otp-groupSmall size: 32px cells, medium font
nds-lg.nds-otp-groupLarge size: 48px cells, XL font

JavaScript API

var group = document.querySelector('.nds-otp-group'); // Get the current value var code = NDS.OTP.getValue(group); // Set a value programmatically NDS.OTP.setValue(group, '1234'); // Clear all inputs and focus first NDS.OTP.clear(group); // Listen for completion (all digits entered) group.addEventListener('nds:otpComplete', function(e) { console.log('Code:', e.detail.value); }); // Listen for any change group.addEventListener('nds:otpChange', function(e) { console.log('Value:', e.detail.value, 'Filled:', e.detail.filled); }); // Listen for clear (fired by NDS.OTP.clear()) group.addEventListener('nds:otpClear', function(e) { console.log('Cleared, value:', e.detail.value); }); // Re-initialize after injecting OTP markup dynamically NDS.OTP.init(); // Validate OTP group manually var result = NDS.Forms.validateOtpGroup(group); // result: { valid: true/false, value: '1234', message: '...' } // Set/clear status NDS.Forms.setStatus({ element: group, status: 'error', message: 'Invalid code' }); NDS.Forms.clearStatus(group);
Last Modified Date: 28/06/2026 - 01:27 PM
Was this page useful?
60% of users said Yes from 2843 Feedbacks