Skip to main content

Simple One-Time Passcode Inputs

By Tyler Sticka

Published on November 11th, 2025

Topics

If you’ve signed into an online service in the last decade, chances are you’ve been asked to fill a one-time passcode (“OTP”) field with a handful of digits from a text, email or authenticator app:

Screenshot of the Slack interface after attempting a sign-in and being asked for a verification code from email. The code entry is divided into separate steps per digit.
Slack’s OTP entry form

Despite the prevalence of this pattern, it seems to cause plenty of anxiety in otherwise level-headed web developers… especially if they’ve fixated on the current trend of segmenting the input to convey the passcode’s length (a new spin on the ol’ input mask).

Why else would so many tumble down the rabbit hole of building their own <input> replacement, stringing multiple <input> elements together, or burdening their project with yet another third-party dependency?

If you find yourself in a similar situation, I have good news! You can ship a fully functional OTP input today without any CSS hacks or JavaScript frameworks.

All you need is some HTML.

A single <input> element: That’s where the OTP magic happens!

<input type="text"
  inputmode="numeric"
  autocomplete="one-time-code"
  maxlength="6">Code language: HTML, XML (xml)

Let’s break down each of its attributes:

  • Even though our passcode will consist of numbers, it isn’t actually a number: A value of 000004 should not be the considered the same as a value of 4. For that reason, we follow the HTML spec and set type="text".
  • inputmode="numeric" enables a numeric virtual keyboard on touch devices.
  • autocomplete="one-time-code" adds support for autofill from password managers or via SMS.
  • maxlength="6" prevents visitors from typing too many characters.

We can opt into client-side validation by adding two more:

<input type="text"
  inputmode="numeric"
  autocomplete="one-time-code"
  maxlength="6"
  pattern="\d{6}"
  required>
Code language: HTML, XML (xml)

pattern defines the code we expect, in this case exactly six ({6}) numeric digits (\d). required tells the browser this field must have a value that satisfies the pattern.

Now all our OTP-specific features are accounted for, but an input is meaningless without context. Let’s fix that by building out a full form with a heading, a label, a submit button and a support link in case something goes wrong:

<form action="…" method="post">
  <h2>Verify Account</h2>
  <label for="otp">
    Enter the 6-digit numeric code sent to +1 (555) 555-5555
  </label>
  <input type="text"
    id="otp"
    inputmode="numeric"
    autocomplete="one-time-code"
    maxlength="6"
    pattern="\d{6}"
    required>
  <button>
    Continue
  </button>
  <a href="…">
    Try another way…
  </a>
</form>Code language: HTML, XML (xml)

Note how the label specifies the intended length and format of the passcode. No input mask, icon or visual affordance can match the accessibility and clarity of straightforward text!

And with that, our OTP pattern is functionally complete!

Since we’ve covered all the critical functionality in our HTML, we’re free to style our form however the project dictates.

In this example, I’ve chosen a large, monospaced font with some letter-spacing to keep every digit of the code distinct and readable. I’m also using the :invalid pseudo class to reduce the visual prominence of the <button> element until the code is valid:

Having a solid foundation in HTML and CSS alone doesn’t preclude us from leveraging JavaScript, too.

Here’s the same demo as before, but with a simple input mask web component to indicate remaining characters:

Because this builds atop existing patterns instead of replacing them outright, the code is tiny: Less than a kilobyte without any optimization or compression.

  • All critical features of a one-time passcode input are possible using HTML alone.
  • Clear labels and instructive text are more important than any visual affordance.
  • Custom design and behavior can be layered on as progressive enhancements.
  • This approach is quicker to implement and avoids many common performance and accessibility pitfalls.

Leave a Comment

Please be kind, courteous and constructive. You may use simple HTML or Markdown in your comments. All fields are required.