splitforms.com
← Back to the journal

input type=number maxlength Not Working? 4 Fixes That Work

Why maxlength is ignored on input type=number, plus four fixes: min/max, oninput slice, text with inputmode numeric, and React controlled inputs.

Start free — 500 submissionsSee pricing →No card, no time limit. Pro is $5 per month.
Short answer
maxlength applies only to text-like inputs, not type="number". Use min/max for numeric range checks, slice on input for a typing cap, or switch to type="text" with inputmode="numeric", pattern, and maxlength for ZIP codes and OTPs. Validate length on the server too.
Try it on your own form

Point any HTML form at one endpoint. 500 free submissions — no card, no time limit — and unlimited forms.

Create free account

maxlength on a number input looks ignored

You add maxlength="5" to limit a five-digit code, but the field still accepts a long string of digits. That is expected browser behavior, not a bug in your framework. The maxlength attribute is defined for text-like input types: text, search, url, tel, email, and password. For type="number", validators skip maxlength, so nothing trims keystrokes or shows a length error.

Number inputs also behave differently from plain text: they accept scientific notation in some browsers, show spin buttons on desktop, and serialize values as numbers—which strips leading zeros. If you need exactly five characters (OTP, card CVC, employee ID), you usually want a digit pattern and a real length limit, not a number type with a ignored attribute.

<label for="code">5-digit code</label>
<input id="code" name="code" type="number" maxlength="5" />
<!-- maxlength has no effect: users can type 6, 10, or 20 digits -->

Fix A: min and max for numeric range (not length)

When the value really is a quantity or amount—not a fixed-width code—use min and max on a number input. They express an allowed numeric range, not a character count. If the value is outside the range, the browser marks the field invalid (:invalid, rangeOverflow) and blocks the form from submitting with a validation message. It does not clamp or change the value.

Someone can still type extra digits while focused; validation runs on submit (and when you call checkValidity()). Pair this with a clear label and, if needed, a custom message via setCustomValidity when the default range text is confusing.

<label for="qty">Quantity (1–99999)</label>
<input
  id="qty"
  name="quantity"
  type="number"
  min="1"
  max="99999"
  step="1"
  required
/>

Choose max="99999" when five digits is the largest value you want to accept, not when you want to stop typing after the fifth character. For the latter, use Fix B or Fix C.

Fix B: slice the value on input

To cap how many characters appear in a number input while keeping type="number", listen for input and truncate el.value. This runs on every keystroke and paste, so the visible string never grows past your limit. It is a UX guard, not a security boundary—treat server checks as mandatory.

const MAX = 5;
const input = document.getElementById("pin");

input.addEventListener("input", () => {
  const raw = input.value;
  if (raw.length <= MAX) return;
  input.value = raw.slice(0, MAX);
});

Paste can insert many digits at once; slicing after paste keeps the field at five characters. If you also use min/max, remember they validate the numeric value, not string length—a sliced value can still fail range checks when the form submits.

Fix C (recommended): text, numeric keyboard, pattern, maxlength

For ZIP codes, OTPs, card CVCs, and other fixed-length digit strings, prefer type="text" with inputmode="numeric", pattern="[0-9]*", and maxlength. Mobile browsers show a number pad; desktop users type digits normally; and maxlength actually applies.

Leading zeros are the main reason to avoid type="number" for postal codes. US ZIP 02134 stored in a number input becomes 2134, which fails shipping APIs and regex tests. Our ZIP code regex tool assumes string values with optional leading zeros—match that in your markup.

<label for="otp">Verification code</label>
<input
  id="otp"
  name="otp"
  type="text"
  inputmode="numeric"
  pattern="[0-9]*"
  maxlength="5"
  autocomplete="one-time-code"
  required
/>

pattern="[0-9]*" nudges validation toward digits; combine it with server-side digit checks because empty string can match * in some cases—use required and backend validation. For SMS one-time passwords, autocomplete="one-time-code" helps mobile browsers suggest the code from the message.

React: controlled input with slice and digit filter

In React, keep the value in state and normalize on every change: strip non-digits with replace(/\D/g, ""), then .slice(0, 5). The input stays controlled, so maxlength-like behavior is consistent across browsers and you avoid mutating the DOM inside event listeners.

"use client";
import { useState } from "react";

export function FiveDigitCode() {
  const [code, setCode] = useState("");

  function onChange(event) {
    const digits = event.target.value.replace(/\D/g, "");
    setCode(digits.slice(0, 5));
  }

  return (
    <label>
      Code
      <input
        name="code"
        type="text"
        inputMode="numeric"
        autoComplete="one-time-code"
        pattern="[0-9]*"
        maxLength={5}
        value={code}
        onChange={onChange}
        required
      />
    </label>
  );
}

Use maxLength={5} on the element as a belt-and-suspenders hint; state slicing is what guarantees length if someone pastes a long string. The same replace(/\D/g, "") pattern works in Vue, Svelte, or plain handlers—only the state API changes.

Always enforce length on the server

Client limits improve UX; they do not protect your API. A crafted request can send a 500-character code field regardless of HTML attributes. In your handler—or on a hosted endpoint such as splitforms—reject values longer than five characters, reject non-digits when you expect digits only, and return a clear 400 response.

function parseFiveDigitCode(raw) {
  if (typeof raw !== "string") return null;
  const trimmed = raw.trim();
  if (trimmed.length === 0 || trimmed.length > 5) return null;
  if (!/^[0-9]+$/.test(trimmed)) return null;
  return trimmed;
}

Mirror the same rules you show in the browser: if the form uses Fix C, require digits and length ≤ 5 on the server. If you use Fix A with max="99999", parse with Number() and compare to your range—do not trust the client to stay inside it.

FAQ

Why does maxlength not work on input type=number?

The HTML specification defines maxlength only for text-like input types: text, search, url, tel, email, and password. For type=number, browsers ignore maxlength entirely, so people can keep typing digits even when you set maxlength="5".

Does max="99999" limit how many characters someone can type?

No. The max attribute sets an allowed numeric range for validation. It does not stop extra keystrokes while typing. Use an oninput slice, a text field with maxlength, or a controlled component if you need a hard character cap.

Should I use type=number for a ZIP code or one-time code?

Usually no. type=number drops leading zeros, so ZIP 02134 becomes 2134. For fixed-length numeric codes, use type="text" with inputmode="numeric", pattern="[0-9]*", and maxlength. Add autocomplete="one-time-code" for SMS OTP fields where supported.

Is client-side length limiting enough?

No. Anyone can bypass the browser with devtools or a direct POST. Always enforce the same maximum length (and allowed characters) on your server or form backend, including splitforms endpoints, before you store or email the value.

Related articles

More practical guidance from tutorials.

Browse the journal →
Tutorials

How to Remove the Arrows From a Number Input (CSS, Tailwind, React)

Hide the number input spin buttons with ::-webkit-inner-spin-button and appearance: textfiel…

7 min readRead →
Tutorials

How to Change the Autofill Background Color of an Input (CSS, Tailwind)

Remove Chrome's yellow autofill background with input:-webkit-autofill, inset box-shadow, -w…

7 min readRead →
Tutorials

autocomplete="off" Not Working? Why Browsers Ignore It (and Fixes)

Chrome and Edge ignore autocomplete=off on login and address fields. Why browsers override i…

8 min readRead →

Explore this topic

Start with the overview, then move into focused guides.

OverviewContact Forms for Every FrameworkStart here →GuideContact Form for Next.jsRead →GuideContact Form for ReactRead →GuideContact Form for VueRead →ReferenceContact form guideOpen →

Building forms with ChatGPT, Claude, Cursor, or v0? Connect the native MCP server and give your agent a production form backend.

Explore the MCP server →

Give your form a production backend.

One endpoint adds delivery, spam filtering, storage, and integrations. Start with 500 free submissions — no card, no time limit.

Create free accountRead the docs →
Secure checkoutSSL encryptionPrivacyProtected
VISAAMERICANEXPRESSstripe