OTP Input
<wa-otp-input>
OTP inputs collect one-time passcodes, PINs, and other fixed-length codes, one character per segment. Use them for SMS verification, two-factor authentication, and invite codes.
This component works with standard <form> elements. See form controls for form submission and client-side validation.
Examples
Label
Use the label attribute to give the field an accessible label. For labels that contain HTML, use the label slot instead.
Hint
Add descriptive hint text with the hint attribute. For hints that contain HTML, use the hint slot instead.
Length
Use the length attribute to change the number of segments. The default is 6.
Type
Use the type attribute to restrict which characters are accepted.
| Type | Accepts | Best for |
|---|---|---|
numeric |
Digits 0–9 | SMS and 2FA codes, PINs |
alpha |
Letters A–Z | Letter-only codes |
alphanumeric |
Letters and digits | Invite codes, serial numbers |
The numeric type also sets the inputmode attribute on the underlying input, so mobile devices show the numeric keyboard.
Format
Use the format attribute to arrange segments into groups with literal separators. The # character marks a segment; any other character becomes a visual separator. Setting format overrides length, so there is no need to specify both.
Case
Use the case attribute to transform characters as they are entered. The default is preserve. Use upper to force uppercase or lower to force lowercase.
Mask
Add the mask attribute to display entered characters using --mask-char (a bullet, •, by default) instead of their real value. The value remains accessible via the value property, masking is display-only, and only visual: screen readers still announce entered characters.
Add the with-mask attribute to also show --mask-char as a hint in each empty segment, so the field reads like a password field even before anything is typed.
Customize the character with the --mask-char custom property. It must be a quoted string.
Appearance
Use the appearance attribute to change the visual style of the segments. The default is outlined.
Size
Use the size attribute to change the size of each segment. The default is m.
Disabled
Use the disabled attribute to prevent interaction.
Readonly
Use the readonly attribute to display a value without allowing edits. Unlike disabled, a readonly field still receives focus and participates in form submission.
Initial Value
Use the value attribute to prefill the segments — for example, when a code arrives in a link's query parameter.
Pasting
Pasting a full code fills all segments in one step. Characters that don't match the type attribute are silently dropped, so pasting "ABC-123" into a numeric field produces 123.
Autofill
The autocomplete attribute defaults to one-time-code, which tells browsers and operating systems to offer autofill for SMS-delivered verification codes. Set autocomplete="off" to disable this — for example, when the field is used for a PIN that shouldn't be suggested by the browser.
On Android, Chrome can also read the code from an incoming SMS with the WebOTP API, no manual entry required. Feature-detect it and set the field's value from the result:
<wa-otp-input id="sms-code" label="Verification code"></wa-otp-input> <script> if ('OTPCredential' in window) { navigator.credentials .get({ otp: { transport: ['sms'] } }) .then(otp => { document.getElementById('sms-code').value = otp.code; }) .catch(() => { // The prompt was dismissed or timed out }); } </script>
Autosubmit
Add the autosubmit attribute to submit the containing form automatically when the last segment is filled. The wa-complete event fires first and is cancelable — call preventDefault() to stop the submission.
To run your own logic on completion instead — verify the code over the network, unlock a button — listen for the wa-complete event without setting autosubmit.
Validation
Add the required attribute to require a value before submission. A partial entry (some segments filled, but not all) is always invalid regardless of required, with the tooShort validity flag set.
Custom Validity
Use the setCustomValidity() method to set a custom validation message. This will prevent the form from submitting and make the browser display the error message you provide. To clear the error, call this function with an empty string.
Customizing
Use the --segment-size, --segment-gap, and --segment-border-radius custom properties along with CSS parts to style the segments, including their border and background.
Combine CSS parts with custom states to react to what the control is doing. For example, coloring the segments green once the code is fully entered (--filled), or red while it's invalid (invalid). This example uses invalid rather than user-invalid so the customization is visible right away, without requiring you to interact with the field first. Type a full code to see it turn green instead:
Accessibility Considerations
The component uses a single visually hidden <input> as the focus and form target — the visible segments are decorative. Screen readers announce it as one text field, named by the label attribute or slot. Always provide a label; without one, the field has no accessible name.
Keyboard interaction follows the single-input model:
| Key | Behavior |
|---|---|
| ← → | Move between segments |
| Tab | Moves focus to the next form control |
| Enter | Submits the containing form |
| Backspace | Clears the current segment and moves back (no character shift) |
| Delete | Clears the current segment without moving |
API
Importing
If you're using the autoloader or a hosted project, components load on demand — no manual import needed. To cherry-pick a component manually, use one of the following snippets.
Import this component directly from the CDN:
import 'https://ka-f.webawesome.com/[email protected]/components/otp-input/otp-input.js';
After installing Web Awesome via npm, import this component:
import '@awesome.me/webawesome/dist/components/otp-input/otp-input.js';
If you're self-hosting Web Awesome, import this component from your server:
import './webawesome/dist/components/otp-input/otp-input.js';
To import this component for React 18 or below, use the following code:
import WaOtpInput from '@awesome.me/webawesome/dist/react/otp-input/index.js';
Slots
Learn more about using slots.
| Name | Description |
|---|---|
hint
|
Optional hint text. Use this for hints that contain HTML. When hint attribute is set it takes priority. |
label
|
An optional label. Use this for labels that contain HTML. When label attribute is set it takes priority. |
Attributes & Properties
Learn more about attributes and properties.
| Name | Description | Reflects |
|---|---|---|
appearanceappearance |
Visual appearance of the segments.
Type
'outlined' | 'filled' | 'filled-outlined' | 'contained'
Default
'outlined'
|
|
autocompleteautocomplete |
The
autocomplete attribute forwarded to the underlying input.
Type
string
Default
'one-time-code'
|
|
autofocusautofocus |
Automatically focuses the field when the page loads.
Type
boolean
Default
false
|
|
autosubmitautosubmit |
When true, the form is submitted automatically once all segments are filled.
Type
boolean
Default
false
|
|
casecase |
Case transformation applied to entered characters.
Type
'preserve' | 'upper' | 'lower'
Default
'preserve'
|
|
defaultValuevalue |
The default value. Used to restore the field on form reset. Reflects the
value HTML attribute.
Type
string | null
|
|
disableddisabled |
Disables the form control.
Type
boolean
Default
false
|
|
effectiveLength |
Number of segments derived from
format (count of #) or length.
Type
number
|
|
form |
By default, form controls are associated with the nearest containing
<form> element. This attribute allows you
to place the form control outside of a form and associate it with the form that has this id. The form must be in
the same document or shadow root for this to work.
Type
HTMLFormElement | null
|
|
formatformat |
Segment format string using
# as a segment placeholder and any other character as a literal separator.
Setting format overrides length (the segment count is derived from the number of # characters).
Type
string
Default
''
|
|
hinthint |
Hint text shown below the segments. Use the
hint slot for HTML content.
Type
string
Default
''
|
|
input |
The real
<input> used for form association and validation (visually hidden).
Type
(HTMLElement & { value: unknown }) | HTMLInputElement | HTMLTextAreaElement | undefined
|
|
labellabel |
A label shown above the segments. Use the
label slot for HTML content.
Type
string
Default
''
|
|
lengthlength |
Number of character segments to display. Overridden by
format when set.
Type
number
Default
6
|
|
maskmask |
When true, entered characters are displayed as
--mask-char instead of their real value.
Type
boolean
Default
false
|
|
namename |
The name of the input, submitted as a name/value pair with form data.
Type
string | null
Default
null
|
|
readonlyreadonly |
Makes the field readonly — the value displays but cannot be edited by the user.
Type
boolean
Default
false
|
|
requiredrequired |
Makes the field required. A partially-filled field is always invalid regardless of this attribute.
Type
boolean
Default
false
|
|
sizesize |
The size of each segment.
Type
'xs' | 's' | 'm' | 'l' | 'xl' | 'small' | 'medium' | 'large'
Default
'm'
|
|
typetype |
Allowed character class.
Type
'numeric' | 'alpha' | 'alphanumeric'
Default
'numeric'
|
|
validationTarget |
Override this to change where constraint validation popups are anchored.
Type
undefined | HTMLElement
|
|
validators |
Validators are static because they have
observedAttributes, essentially attributes to "watch"
for changes. Whenever these attributes change, we want to be notified and update the validator.
Type
Validator[]
Default
[]
|
|
value |
The current value of the OTP field, submitted as a name/value pair with form data.
Type
string
|
|
withMaskwith-mask |
When true, empty segments show
--mask-char as a hint instead of appearing blank, similar to
how a password field communicates its expected length before anything is typed.
Type
boolean
Default
false
|
|
Methods
Learn more about methods.
| Name | Description | Arguments |
|---|---|---|
blur() |
Removes focus from the field. | |
clear() |
Clears the current value and returns focus to the field. | |
focus() |
Focuses the field. |
options: FocusOptions
|
formStateRestoreCallback() |
Called when the browser is trying to restore element’s state to state in which case reason is "restore", or when the browser is trying to fulfill autofill on behalf of user in which case reason is "autocomplete". In the case of "restore", state is a string, File, or FormData object previously set as the second argument to setFormValue. |
state: string | File | FormData | null,
reason: 'autocomplete' | 'restore'
|
resetValidity() |
Reset validity is a way of removing manual custom errors and native validation. | |
select() |
Selects all entered characters in the hidden input. | |
setCustomValidity() |
Do not use this when creating a "Validator". This is intended for end users of components. We track manually defined custom errors so we don't clear them on accident in our validators. |
message: string
|
Events
Learn more about events.
| Name | Description |
|---|---|
blur |
Emitted when the control loses focus. |
change |
Emitted when the value changes and the field loses focus. |
focus |
Emitted when the control gains focus. |
input |
Emitted when a character is entered or removed. |
wa-clear |
Emitted when the control's value is cleared. |
wa-complete |
Emitted once when all segments are filled. Cancelable — call preventDefault() to stop autosubmit from submitting the form for this completion. |
wa-invalid |
Emitted when the form control has been checked for validity and its constraints aren't satisfied. |
CSS Custom Properties
Learn more about CSS custom properties.
| Name | Description |
|---|---|
--mask-char |
Character shown in place of entered values when
mask is set, and as a hint in empty segments when with-mask is set.
Default
'•'
|
--segment-border-radius |
Corner radius of each segment.
Default
var(--wa-form-control-border-radius)
|
--segment-gap |
Gap between segments (not used in
contained appearance).
Default
var(--wa-space-xs)
|
--segment-size |
Width and height of each segment cell.
Default
2.5em
|
Custom States
Learn more about custom states.
| Name | Description | CSS selector |
|---|---|---|
--blank |
Applied when no characters have been entered. |
:state(--blank)
|
--filled |
Applied when all segments are filled. |
:state(--filled)
|
disabled |
Applied when the component is disabled. |
:state(disabled)
|
readonly |
Applied when the component is readonly. |
:state(readonly)
|
user-invalid |
Applied when validation fails after interaction. |
:state(user-invalid)
|
CSS Parts
Learn more about CSS parts.
| Name | Description | CSS selector |
|---|---|---|
hint |
The hint element. |
::part(hint)
|
label |
The label element. |
::part(label)
|
segment |
An individual character segment cell. |
::part(segment)
|
segment-literal |
Inert literal text between segment groups (e.g. space or dash). |
::part(segment-literal)
|
segments |
The wrapper around all segment cells and separators. |
::part(segments)
|