OTP / PIN Input
Configurable verification codes and PINs with Material slots, native forms, and Maskito patterns.
Basic example
A Material input for verification codes and PINs. Choose the number of characters, type or paste a code, and edit any slot. One native input preserves keyboard selection, labels, form values, and browser one-time-code autofill support.
Loading OTP input…
Submit a complete code to inspect the native form value.<div class="input-field outlined">
<input id="verification-code" name="code" type="text" placeholder=" "
data-otp data-otp-length="6" autocomplete="one-time-code"
aria-describedby="verification-help" required>
<label for="verification-code">Verification code</label>
<span id="verification-help" class="supporting-text">Enter all six digits.</span>
</div>Use the same input-field outlined wrapper as other text inputs. OTP slots share the 4px corner radius and 1px outline, with a 2px active border. The label stays above the group so it remains readable across the individual slots.
import { OtpInput } from 'kmaterialize';
const field = OtpInput.init(input, {
length: 6,
characters: 'digits',
onComplete: () => { status.textContent = 'Code complete. Ready to submit.'; }
});
await field.ready;Completion means every slot is filled; verification happens in your application. The component does not submit the form automatically.
Setup
Start with the kmaterialize CSS and JavaScript from Getting started, then follow the component setup below.
npm install kmaterialize @maskito/core<div class="input-field outlined">
<input id="verification" name="code" type="text" data-otp
data-otp-length="6" autocomplete="one-time-code" required>
<label for="verification">Verification code</label>
</div>import 'kmaterialize/dist/css/materialize.css';
import { AutoInit, OtpInput } from 'kmaterialize';
AutoInit();
const field = OtpInput.getInstance(input);
await field.ready;
await field.setValue('012345'); // Keeps the leading zero; emits input.
field.isComplete(); // true
await field.clear();
await field.refresh(); // Sync after assigning input.value directly.
field.focus();
field.destroy(); // Restores the native input and attributes.Use no-autoinit for explicit initialization. Await ready before calling the API. Without a bundler, expose Maskito core exports as window.MaskitoCore before initializing M.OtpInput. The plain native input remains available if the optional library cannot load.
Examples and variants
Bottom-line variant
Omit outlined for the same bottom-line treatment as a standard text input. Each slot has a 1px underline, increasing to 2px on the active slot. Length, masks, paste, and validation work the same way.
<div class="input-field">
<input id="verification" name="code" type="text" placeholder=" "
data-otp data-otp-length="6" autocomplete="one-time-code"
aria-describedby="verification-support">
<label for="verification">Verification code</label>
<span id="verification-support" class="supporting-text">Enter your six-digit code.</span>
</div>Use M.AutoInit(), or initialize explicitly with OtpInput.init(input). Add outlined to the wrapper to switch back to boxed slots.
Hidden PIN
<div class="input-field outlined">
<input id="pin" type="password" data-otp data-otp-length="4"
autocomplete="off" placeholder=" ">
<label for="pin">Four-digit PIN</label>
</div>Grouped digits
groupSize: 3 adds space after each group of three. The value remains six digits, without a separator.
OtpInput.init(input, { length: 6, groupSize: 3 });Custom Maskito pattern
Pattern: AA-####
const license = OtpInput.init(input, {
pattern: 'AA-####',
maskOptions: {
overwriteMode: 'replace',
postprocessors: [({ value, selection }) => ({
value: value.toUpperCase(), selection
})]
}
});
await license.ready;
await license.setValue('fx2048');
license.getValue(); // "FX-2048" — also the native form value
license.getUnmaskedValue(); // "FX2048"The pattern determines the number of editable slots: # is a digit, A an ASCII letter, and * an ASCII letter or digit. Other characters are fixed separators; a backslash escapes a token. If you also supply length, it must match the pattern's editable slots.
Pass Maskito processors, plugins, and overwrite options through maskOptions. The component owns the fixed mask so the slots and value stay aligned. Processors should preserve the pattern's positions. See more examples on the Input Masks page.
Native field states
Use native required, disabled, readonly, and aria-describedby. Incomplete nonempty values fail pattern validation. For a server-rejected code, set aria-invalid="true" and describe the error beside the field. Clear that attribute when the error is resolved.
Validation
Use required for an empty code. OtpInput validates incomplete values against the slot pattern. A server-rejected code uses aria-invalid="true" with a clear message; clear it when the user corrects the code.
These previews keep their error state visible. In your form, show errors after checking a field or submitting, then clear them when the value is valid.
For text fields, use .input-field.error, aria-invalid="true", and a message linked with aria-describedby. Remove the error state when resolved. Use aria-live="polite" for messages updated dynamically.
Required
Incomplete code
<div class="input-field outlined error">
<input id="validation-otp" type="text" data-otp data-otp-length="6" data-otp-align="left" required autocomplete="one-time-code" placeholder=" " value="" aria-invalid="true" aria-describedby="validation-otp-error">
<label for="validation-otp">Verification code</label>
<span id="validation-otp-error" class="supporting-text"><i class="material-icons" aria-hidden="true">error</i> Enter the six-digit verification code.</span>
</div>
<div class="input-field error">
<input id="validation-otp-incomplete" type="text" data-otp data-otp-length="6" data-otp-align="left" required placeholder=" " value="123" aria-invalid="true" aria-describedby="validation-otp-incomplete-error">
<label for="validation-otp-incomplete">Verification code</label>
<span id="validation-otp-incomplete-error" class="supporting-text"><i class="material-icons" aria-hidden="true">error</i> Enter all six digits.</span>
</div>Reference
Options and methods
| Option | Behavior |
|---|---|
length | 1–32 editable slots; defaults to 6 without a pattern. |
characters | digits (default) or alphanumeric. |
pattern | A fixed Maskito shorthand pattern, such as ###-### or AA-####. |
groupSize | Visual grouping only; 0 disables it. |
masked | Optional boolean; otherwise preserves the original input type. |
maskOptions | Maskito processing, plugins, and overwrite mode. Default mode is replace. |
onComplete | Called with the formatted value and instance when an edit produces a new complete code. |
The bubbling otpcomplete event exposes detail.value and detail.unmaskedValue. Initialization, reset, refresh, and setValue(value, false) do not emit completion events. Standard input/change events and FormData use the original input.
Markup options: data-otp-length, data-otp-characters, data-otp-pattern, data-otp-group-size, and data-otp-masked. JavaScript options take precedence.
Customize --otp-slot-size, --otp-slot-gap, and --otp-slot-radius on .otp-input. Colors follow the current Material theme. Slots wrap on narrow screens; codes remain ordered left to right in RTL pages.