Text and choice controls

Numbers, dates, and ranges

Use numeric and date-related controls without assuming every value that looks numeric should use a number input.

Use type="number" for a quantity. Something you can compare, add up, or increase by one.

<label for="seats">Number of seats</label>
<input id="seats" name="seats" type="number" min="1" max="12" step="1">

The browser shows stepper arrows and refuses to submit 0 or 13. step="1" also rejects 2.5.

Notice that the request still carries text. The body says seats=4, a string. The server must parse it, reject anything that isn’t a number, and check the range again. The HTML attributes are a hint to the browser, not a rule the server can rely on.

Not everything with digits is a number

Postal codes, card numbers, and phone numbers are identifiers. You never add two postal codes together. They can start with a zero, and they often contain spaces or dashes.

A number input is the wrong tool for them. Type 0161 496 0000 into one and the browser can’t make sense of it, so input.value becomes an empty string. Use a text input and ask for the numeric keyboard with inputmode:

<label for="postcode">Postcode</label>
<input id="postcode" name="postcode" inputmode="numeric" autocomplete="postal-code">

You get the convenient keyboard on phones without the number-only behavior.

Dates

Date controls submit normalized strings. A type="date" input always sends 2026-07-30, whatever format the person saw on screen. That part is easy.

The hard part is what the value means. 2026-07-30 could be a calendar date, like a birthday. type="datetime-local" sends 2026-07-30T09:30, with no time zone at all. Is that the visitor’s local time, or the server’s?

Decide this before you store anything. Don’t let a library guess a time zone for you.

Ranges

A range input is a slider. Use it when the rough position matters more than the exact number, like a volume level:

<label for="volume">Volume</label>
<input id="volume" name="volume" type="range" min="0" max="100" value="50">

A slider alone is hard to use precisely, because you can’t see the current value. My advice is to show it next to the control with an <output> element and a few lines of JavaScript. If people need to type an exact number, use a number input instead.

Try submitting the seats form with 0, 12, 13, and an empty value. The browser blocks two of them. Then send the same values with curl and confirm the server rejects exactly what the HTML says it rejects.

Lesson completed