Back to Article index

Before You Use input[type=number]

When input[type=number] is appropriate, what its semantics imply, and when a text input with inputmode is the better choice.

Published
December 27, 2023
Read in Japanese

Forms accept numbers. They accept dates, too, often under strict constraints. I had used input[type=number] without thinking carefully about what the element means. This article draws the line around its proper use.

Why input[type=number] can feel awkward

Use input[type=number] when incrementing or decrementing a numeric value is useful. MDN likewise recommends it for incremental numbers rather than any string that happens to contain digits.

The element has an implicit spinbutton role. It is not simply a field that “accepts digits”; it represents a numeric value that can be stepped within a range.

For a quantity such as the number of products in an order, start with the native input[type=number] and its min, max, and step attributes. Replacing it with a custom role="spinbutton" just to change its appearance means recreating keyboard behavior, value updates, and assistive-technology announcements yourself.

Values made of digits are not always numbers

A credit-card number or postal code contains digits, but it is not a number used in a calculation. For these values, use type="text" and consider inputmode="numeric" to request a numeric virtual keyboard. A pattern can express constraints such as the required number of digits.

Do not rely on the title attribute alone to explain those constraints. Show the guidance as visible text and associate it with the input through aria-describedby, so people can understand the requirement before submitting the form.

<form>
  <label for="digit">Verification code</label>
  <input
    id="digit"
    name="digit"
    type="text"
    inputmode="numeric"
    pattern="\d{3}"
    aria-describedby="digit-description" />
  <span id="digit-description">Enter a three-digit number.</span>
  <button>Submit</button>
</form>

For a custom validation message, connect the input and message with aria-describedby, expose the invalid state with aria-invalid, and consider role="status" or aria-live when a message is inserted dynamically after submission.

<label for="digit-with-error">Verification code</label>
<input
  id="digit-with-error"
  name="digit"
  type="text"
  inputmode="numeric"
  pattern="\d{3}"
  aria-invalid="true"
  aria-describedby="digit-error" />
<p id="digit-error" role="status" aria-live="polite">
  Enter a three-digit number.
</p>

The important question is not whether a value looks numeric. Ask whether it is a number, whether stepping is useful, and which semantics match the task. The problem was not that input[type=number] was difficult to use; I had chosen it for a use case it was not designed to handle.

Choosing input[type=date]

input[type=date] submits either an empty string or a date string in yyyy-mm-dd format, regardless of how the control displays the date. Its UI and interaction can vary across browsers and operating systems.

Start by considering the native input, which gives the browser responsibility for localization and keyboard interaction. Reach for a date-picker component when the native control cannot meet a real requirement, such as restricting a complex set of dates or selecting a range. A custom date picker is difficult to make accessible, so appearance alone is not a good reason to replace the native control.

Conclusion

Familiar elements are easy to use without revisiting their purpose. Choosing inputs by semantics and interaction—not appearance—leads to forms that are easier for more people to use.

References