Font Awesome Build Awesome
Try SSR Server-side rendering (SSR) generates component HTML on the server before the page loads, improving SEO and initial load time. Use the switch to see Web Awesome components render with and without SSR.
Search this website ⌘KCtrl+K Light Dark System Docs Select Color Scheme Default Awesome Shoelace Active Brutalist Glossy Matter Mellow Playful Premium Tailspin Docs Select Theme View Project on GitHub Star Project on GitHub
Start Components Docs Help
Web Awesome Font Awesome Build Awesome
Search this site… /
Try SSR Server-side rendering (SSR) generates component HTML on the server before the page loads, improving SEO and initial load time. Use the switch to see Web Awesome components render with and without SSR.
Light Dark System Docs Select Color Scheme Default Awesome Shoelace Active Brutalist Glossy Matter Mellow Playful Premium Tailspin Docs Select Theme

Getting Started

  • Installation
  • Usage
  • Forms
  • Localization
  • Frameworks
  • Using with AI
  • Figma Design Kit ProThis requires access to Web Awesome Pro
  • Server Rendering

Resources

  • Accessibility
  • Browser Support
  • Contributing
  • Patterns ProPatterns require access to Web Awesome Pro
  • Migrating from Shoelace
  • Visual Tests
  • Changelog
  • Help & Support

Theming & Utilities

  • Overview
  • Built-in Themes
  • Color Palettes
  • Design Tokens
  • Customizing & Theming
  • CSS Utilities

Actions

  • Button
  • Button Group
  • Copy Button
  • Dropdown
    • Dropdown Item

Forms

  • Checkbox
  • Checkbox Group
  • Color Picker
  • Input
  • Known Date
  • Number Input
  • OTP Input
  • Radio Group
    • Radio
  • Rating
  • Select
    • Option
  • Slider
  • Switch
  • Textarea
  • Time Input
  • Data Grid Planned A Web Awesome Kickstarter stretch goal!

Layout

  • Accordion
    • Accordion Item
  • Card
  • Details
  • Dialog
  • Divider
  • Drawer
  • Page
  • Scroller
  • Split Panel

Navigation

  • Breadcrumb
    • Breadcrumb Item
  • Tab Group
    • Tab
    • Tab Panel
  • Tree
    • Tree Item

Feedback

  • Badge
  • Callout
  • Progress Bar
  • Progress Ring
  • Skeleton
  • Spinner
  • Tag
  • Toast
    • Toast Item
  • Tooltip

Media

  • Animated Image
  • Avatar
  • Carousel
    • Carousel Item
  • Comparison
  • Icon
  • Markdown
  • QR Code
  • Zoomable Frame

Data Viz

  • Advanced Usage

Helpers

  • Animation
  • Format Bytes
  • Format Date
  • Format Number
  • Include
  • Intersection Observer
  • Mutation Observer
  • Popover
  • Popup
  • Random Content
  • Relative Time
  • Resize Observer

OTP Input

  • Examples
  • Label
  • Hint
  • Length
  • Type
  • Format
  • Case
  • Mask
  • Appearance
  • Size
  • Disabled
  • Readonly
  • Initial Value
  • Pasting
  • Autofill
  • Autosubmit
  • Validation
  • Custom Validity
  • Customizing
  • Accessibility Considerations
  • API
  • Importing
  • Slots
  • Attributes & Properties
  • Methods
  • Events
  • CSS Custom Properties
  • Custom States
  • CSS Parts
On This Page...
  • Examples
  • Label
  • Hint
  • Length
  • Type
  • Format
  • Case
  • Mask
  • Appearance
  • Size
  • Disabled
  • Readonly
  • Initial Value
  • Pasting
  • Autofill
  • Autosubmit
  • Validation
  • Custom Validity
  • Customizing
  • Accessibility Considerations
  • API
  • Importing
  • Slots
  • Attributes & Properties
  • Methods
  • Events
  • CSS Custom Properties
  • Custom States
  • CSS Parts

OTP Input

<wa-otp-input>
Experimental Forms Since 3.11

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.

<wa-otp-input label="Verification code"></wa-otp-input>

This component works with standard <form> elements. See form controls for form submission and client-side validation.

Examples

Link to This Section

Label

Link to This Section

Use the label attribute to give the field an accessible label. For labels that contain HTML, use the label slot instead.

<wa-otp-input label="Security code"></wa-otp-input>

Hint

Link to This Section

Add descriptive hint text with the hint attribute. For hints that contain HTML, use the hint slot instead.

<wa-otp-input label="Sign-in code" hint="Check your email for a 6-digit code."></wa-otp-input>

Length

Link to This Section

Use the length attribute to change the number of segments. The default is 6.

<div class="wa-stack">
  <wa-otp-input label="Card PIN" length="4"></wa-otp-input>
  <wa-otp-input label="Backup code" length="8"></wa-otp-input>
</div>

Type

Link to This Section

Use the type attribute to restrict which characters are accepted.

<div class="wa-stack">
  <wa-otp-input label="Numeric" type="numeric"></wa-otp-input>
  <wa-otp-input label="Alpha" type="alpha"></wa-otp-input>
  <wa-otp-input label="Alphanumeric" type="alphanumeric"></wa-otp-input>
</div>
Type Accepts Best for
numeric default 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

Link to This Section

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.

<div class="wa-stack">
  <!-- Two groups of three with a space: e.g. "ABC DEF" -->
  <wa-otp-input label="Invite code" type="alphanumeric" format="### ###"></wa-otp-input>
  <!-- Three groups of four joined by dashes: e.g. "1234-5678-9012" -->
  <wa-otp-input label="License key" type="alphanumeric" format="####-####-####"></wa-otp-input>
</div>

Case

Link to This Section

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.

<div class="wa-stack">
  <wa-otp-input label="Upper" type="alpha" case="upper"></wa-otp-input>
  <wa-otp-input label="Lower" type="alpha" case="lower"></wa-otp-input>
</div>

Mask

Link to This Section

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.

<wa-otp-input label="PIN" mask length="4"></wa-otp-input>

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.

<wa-otp-input label="PIN" mask with-mask length="4"></wa-otp-input>

Customize the character with the --mask-char custom property. It must be a quoted string.

<wa-otp-input id="custom-mask-char" label="PIN" mask with-mask length="4"></wa-otp-input>

<style>
  #custom-mask-char {
    --mask-char: '*';
  }
</style>

Appearance

Link to This Section

Use the appearance attribute to change the visual style of the segments. The default is outlined.

<div class="wa-stack">
  <wa-otp-input label="Outlined" appearance="outlined"></wa-otp-input>
  <wa-otp-input label="Filled" appearance="filled"></wa-otp-input>
  <wa-otp-input label="Filled outlined" appearance="filled-outlined"></wa-otp-input>
  <wa-otp-input label="Contained" appearance="contained"></wa-otp-input>
</div>

Size

Link to This Section

Use the size attribute to change the size of each segment. The default is m.

<div class="wa-stack">
  <wa-otp-input label="Extra small" size="xs"></wa-otp-input>
  <wa-otp-input label="Small" size="s"></wa-otp-input>
  <wa-otp-input label="Medium" size="m"></wa-otp-input>
  <wa-otp-input label="Large" size="l"></wa-otp-input>
  <wa-otp-input label="Extra large" size="xl"></wa-otp-input>
</div>

Disabled

Link to This Section

Use the disabled attribute to prevent interaction.

<wa-otp-input label="Verification code" disabled value="391824"></wa-otp-input>

Readonly

Link to This Section

Use the readonly attribute to display a value without allowing edits. Unlike disabled, a readonly field still receives focus and participates in form submission.

<wa-otp-input label="Confirmation code" readonly value="483920"></wa-otp-input>

Initial Value

Link to This Section

Use the value attribute to prefill the segments — for example, when a code arrives in a link's query parameter.

<wa-otp-input label="Magic link code" value="271828"></wa-otp-input>

Pasting

Link to This Section

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.

Copy code: 314159
<wa-copy-button value="314159">
  <wa-button appearance="filled">
    <wa-icon slot="start" name="clipboard"></wa-icon>
    Copy code: 314159
  </wa-button>
</wa-copy-button>

<wa-divider></wa-divider>

<wa-otp-input label="Paste your code below"></wa-otp-input>

Autofill

Link to This Section

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

Link to This Section

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.

<form class="autosubmit">
  <wa-otp-input name="code" label="SMS verification code" autosubmit></wa-otp-input>
</form>

<script type="module">
  const form = document.querySelector('.autosubmit');

  form.addEventListener('submit', event => {
    event.preventDefault();
    alert(`Submitted code: ${new FormData(event.target).get('code')}`);
  });
</script>

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

Link to This Section

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.


Continue
<form class="validation">
  <wa-otp-input name="code" label="Two-factor code" required></wa-otp-input>
  <br />
  <wa-button appearance="filled" type="submit">Continue</wa-button>
</form>

<script type="module">
  const form = document.querySelector('.validation');

  form.addEventListener('submit', event => {
    event.preventDefault();
    alert('Code accepted!');
  });
</script>

Custom Validity

Link to This Section

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.


Verify
<form class="custom-validity">
  <wa-otp-input name="code" label="Verification code" hint="The correct code is 314159." required></wa-otp-input>
  <br />
  <wa-button appearance="filled" type="submit">Verify</wa-button>
</form>

<script type="module">
  const form = document.querySelector('.custom-validity');
  const otp = form.querySelector('wa-otp-input');

  otp.addEventListener('input', () => {
    // Only flag complete entries — partial input is already invalid via tooShort
    const isValid = otp.value.length < otp.length || otp.value === '314159';
    otp.setCustomValidity(isValid ? '' : 'That code didn’t match. Check your device and try again.');
  });

  form.addEventListener('submit', event => {
    event.preventDefault();
    alert('Code accepted!');
  });
</script>

Customizing

Link to This Section

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.

<wa-otp-input id="styled-otp" label="Card PIN" length="4"></wa-otp-input>

<style>
  #styled-otp {
    --segment-size: 3.5rem;
    --segment-gap: 0.75rem;
    --segment-border-radius: 0.75rem;
  }

  #styled-otp::part(segment) {
    font-size: 1.5rem;
    font-weight: 700;
    background-color: var(--wa-color-brand-fill-quiet);
    border-color: var(--wa-color-brand-border-loud);
  }
</style>

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:

<wa-otp-input class="stateful-otp" label="Two-factor code" required></wa-otp-input>

<style>
  .stateful-otp:state(--filled)::part(segment) {
    background-color: var(--wa-color-success-fill-quiet);
    border-color: var(--wa-color-success-border-loud);
  }

  .stateful-otp:state(invalid)::part(segment) {
    border-color: var(--wa-color-danger-border-loud);
  }
</style>

Accessibility Considerations

Link to This Section

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

Link to This Section

Importing

Link to This Section

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.

CDN npm Self-Hosted React

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

Link to This Section

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

Link to This Section

Learn more about attributes and properties.

Name Description Reflects
appearance
appearance
Visual appearance of the segments.
Type 'outlined' | 'filled' | 'filled-outlined' | 'contained'
Default 'outlined'
autocomplete
autocomplete
The autocomplete attribute forwarded to the underlying input.
Type string
Default 'one-time-code'
autofocus
autofocus
Automatically focuses the field when the page loads.
Type boolean
Default false
autosubmit
autosubmit
When true, the form is submitted automatically once all segments are filled.
Type boolean
Default false
case
case
Case transformation applied to entered characters.
Type 'preserve' | 'upper' | 'lower'
Default 'preserve'
defaultValue
value
The default value. Used to restore the field on form reset. Reflects the value HTML attribute.
Type string | null
disabled
disabled
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
format
format
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 ''
hint
hint
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
label
label
A label shown above the segments. Use the label slot for HTML content.
Type string
Default ''
length
length
Number of character segments to display. Overridden by format when set.
Type number
Default 6
mask
mask
When true, entered characters are displayed as --mask-char instead of their real value.
Type boolean
Default false
name
name
The name of the input, submitted as a name/value pair with form data.
Type string | null
Default null
readonly
readonly
Makes the field readonly — the value displays but cannot be edited by the user.
Type boolean
Default false
required
required
Makes the field required. A partially-filled field is always invalid regardless of this attribute.
Type boolean
Default false
size
size
The size of each segment.
Type 'xs' | 's' | 'm' | 'l' | 'xl' | 'small' | 'medium' | 'large'
Default 'm'
type
type
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
withMask
with-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

Link to This Section

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

Link to This Section

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

Link to This Section

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

Link to This Section

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

Link to This Section

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)
Need a hand? Report a bug Ask for help
Go Make Something Awesome
Version 3.10.0 © Fonticons, Inc.
  • Terms
  • Privacy
  • Refunds
  • Core License
  • Pro License

Quick Links

  • Components
  • CSS Utilities
  • Theming
  • Using with AI
  • Changelog
  • Help & Support

Recent Searches

    D'oh! No results for “”

    Suggest on GitHub Ask on Discord
    Navigate Select
    Close Esc