The behaviour of <input> changes substantially with the type you pick. This article goes type by type, so you can tell which ones are safe to use as-is and which to avoid in production. Alongside the per-type quirks it also covers <textarea>, <datalist>, field-sizing and accent-color.
This article was published in 2024 and revised in September 2026. The original code contained an error: a pattern attribute on a <textarea>. That has been corrected, and per-type guidance added.
patternapplies to<input>only. It has no effect on<textarea>. Useminlengthfor a minimum lengthinputmode,autocomplete,listandpatternare not 2024 additions
Sponsored
Which types to use, and which to avoid
The short version: keep number to a narrow set of uses, and avoid month and week. The date family and range are usable if you accept the differences between browsers.
| type | Verdict | Why |
|---|---|---|
text / email / tel / url / password |
Use freely | Behaviour is stable |
search |
Use | Gets a clear button |
date / time / datetime-local |
With care | UI differs a lot between browsers |
number |
Narrow use only | Scroll wheel changes it; leading zeros vanish |
range |
With care | Not for precise values |
color |
Use | Opens the OS colour picker |
file |
Use | accept and capture work well |
checkbox / radio |
Use | Standard keyboard behaviour comes free |
month / week |
Avoid | Firefox falls back to a plain text field |
When the choice is not obvious, two questions settle it: “is this a value you increment?” and “is the format the same worldwide?” The awkward types follow, in order.
type=”date”: what to know first
The display format comes from the OS and browser locale, and you cannot change it—not with CSS, not with an attribute. An English locale shows “06/09/2024” where a Japanese one shows “2024/06/09”.
<label for="birthday">Date of birth</label>
<input type="date" id="birthday" name="birthday"
min="1900-01-01" max="2024-12-31"
autocomplete="bday">
Three things to know:
valueis alwaysYYYY-MM-DD, whatever is displayed. That is what gets submitted and what scripts readminandmaxalso useYYYY-MM-DD, and dates outside the range cannot be chosen- Empty means an empty string, and
valueAsDatereturnsnull
Careful with valueAsDate
const input = document.getElementById('birthday');
input.value; // "2024-06-09" (string)
input.valueAsDate; // Date object — but UTC-based
valueAsDate is interpreted as midnight UTC. In a non-UTC timezone the date can come out one day off, so for date-only values, working with the string is safer.
// can be off by a day
const d = input.valueAsDate;
console.log(d.getDate()); // the previous day in some timezones
// stay with the string
const [y, m, day] = input.value.split('-').map(Number);
One more thing: the icon that opens the calendar is small, and on a phone it sits somewhere awkward to hit. How to make the picker open from anywhere in the field with showPicker(), and the constraints that come with it, are covered in HTML form validation and submission without a library.
Sponsored
type=”number”: narrow it down
Do not reach for it whenever a field holds digits. Postal codes, phone numbers and card numbers are all wrong fits.
- Leading zeros are dropped (
0120becomes120) - Hyphens cannot be typed
- The scroll wheel silently changes the value
- Long numbers can render in exponential notation
It fits values you want to increment—quantities, ages, anything you nudge up and down. Even then, always give it min, max and step.
<!-- quantity: type="number" is right here -->
<label for="qty">Quantity</label>
<input type="number" id="qty" name="qty" value="1" min="1" max="99" step="1">
For “digits but not a quantity”, such as a postal code, use type="text" and switch only the keyboard with inputmode="numeric". The full attribute combination is in faster mobile forms with inputmode, autocomplete and enterkeyhint.
Disable the wheel to prevent accidental edits while the field has focus.
document.querySelectorAll('input[type="number"]').forEach((el) => {
el.addEventListener('wheel', (e) => {
if (document.activeElement === el) e.preventDefault();
}, { passive: false });
});
{ passive: false } is required, or preventDefault() is ignored.
To read the value, valueAsNumber saves a conversion—value is a string.
const qty = document.getElementById('qty');
qty.value; // "1" (string)
qty.valueAsNumber; // 1 (number; NaN when empty)
An empty field gives you NaN. Screen for it with Number.isNaN() before you calculate anything.
type=”file”: accept and capture
type="file" is one of the straightforward ones. accept narrows the picker dialog and capture opens a phone’s camera directly.
<!-- multiple images -->
<input type="file" name="photos" accept="image/*" multiple>
<!-- open the rear camera on mobile -->
<input type="file" name="doc" accept="image/*" capture="environment">
<!-- filter by extension -->
<input type="file" name="sheet" accept=".csv,.xlsx,text/csv">
accept is not validation. Users can switch the dialog to “All Files”. Always check type and size on the server.
The values for capture are environment (rear camera) and user (front camera).
To reject a file on the client after it is chosen:
const MAX = 5 * 1024 * 1024; // 5MB
fileInput.addEventListener('change', () => {
for (const file of fileInput.files) {
if (!file.type.startsWith('image/')) {
alert('Please choose an image file');
fileInput.value = ''; // clear the selection
return;
}
if (file.size > MAX) {
alert('Please choose a file under 5MB');
fileInput.value = '';
return;
}
}
});
fileInput.value = '' clears the selection. Without it, the rejected filename stays on screen.
Sponsored
type=”color” and type=”range”
Both are straightforward. color opens the OS colour picker, and range takes tick marks through list. There is one thing to watch: a range shows no numeric value.
<label for="theme">Theme colour</label>
<input type="color" id="theme" name="theme" value="#2a7fff">
<label for="volume">Volume</label>
<input type="range" id="volume" name="volume"
min="0" max="100" step="5" value="50"
list="volume-marks">
<datalist id="volume-marks">
<option value="0"></option>
<option value="50"></option>
<option value="100"></option>
</datalist>
<output for="volume" id="volume-out">50</output>
Adding list to a range gives you tick marks. And since the current value is invisible, always pair a range with <output>.
const volume = document.getElementById('volume');
const out = document.getElementById('volume-out');
volume.addEventListener('input', () => {
out.textContent = volume.value;
});
Know the difference between input and change: input fires continuously while the thumb is being dragged; change fires once on release. Update the display on input, save to the server on change.
Recolour checkboxes and sliders with accent-color
Recolouring checkboxes, radios, range and progress used to mean rebuilding them with appearance: none. accent-color does it in one line.
:root {
accent-color: #2a7fff;
}
You keep the native appearance, keyboard behaviour and accessibility while swapping in your brand colour. Rebuilding controls with appearance: none purely for looks means reimplementing focus rings and screen reader behaviour yourself—try this first.
Can you use the switch attribute yet?
Not in production. <input type="checkbox" switch> shipped in Safari 17.4, but as of September 2026 Chrome and Firefox do not support it.
That said, unsupported browsers render it as a working checkbox, so it is safe as progressive enhancement.
<label>
<input type="checkbox" switch name="notify" checked>
Receive notifications
</label>
If you need an identical switch in every browser today, you have to build it in CSS. Even then, a checkbox recoloured with accent-color is more robust for keyboard and screen reader users than one rebuilt with appearance: none.
pattern does not work on textarea
This was the error in the original article. pattern belongs to <input> and does not exist on <textarea>.
<!-- has no effect -->
<textarea required pattern=".{10,}" title="At least 10 characters"></textarea>
<!-- correct: use minlength -->
<label for="feedback">Feedback</label>
<textarea id="feedback" name="feedback"
required minlength="10" maxlength="1000"
placeholder="Tell us what you think"></textarea>
Two things about minlength: it does not apply until the user has typed something (required fires first), and newlines count as characters.
Auto-resizing height is one line with field-sizing
Growing a field with its content used to mean measuring scrollHeight in JavaScript. CSS field-sizing: content reduces it to one line. It reached Baseline in June 2026.
textarea {
field-sizing: content;
min-height: 4lh; /* keep at least four lines */
max-height: 20lh; /* stop it running away */
width: 100%;
}
input[type="text"] {
field-sizing: content;
min-width: 8ch;
}
Setting min-height and max-height together is the trick. Without them the field shrinks to one line when empty and grows to fill the screen with long text. The lh unit (one line’s height) lets you express the limits in lines, which is easier to reason about.
For a remaining-characters counter, pair it with maxlength.
const ta = document.getElementById('feedback');
const counter = document.getElementById('counter');
ta.addEventListener('input', () => {
counter.textContent = `${ta.value.length} / ${ta.maxLength}`;
});
datalist accepts values outside the list
<datalist> is convenient autocompletion, but you need to understand that values outside the list can still be entered. If the choices must be enforced, use <select>.
<label for="search">Search products</label>
<input type="search" id="search" list="products"
enterkeyhint="search" placeholder="Product name">
<datalist id="products">
<option value="Smartphone"></option>
<option value="Laptop"></option>
<option value="Tablet"></option>
<option value="Headphones"></option>
</datalist>
The limitations worth knowing before you use it:
- Values outside the list submit fine—to reject them, use
patternor validate on the server - The dropdown cannot be styled with CSS; it is browser UI
- Filtering behaviour is browser-dependent—prefix versus substring matching is not standardised
- It gets unwieldy past a few dozen entries; move to
<select>or a custom combobox
Where the options are fixed and free text is unwelcome—a list of states or prefectures—<select> is the right answer. <datalist> suits cases like a product search, where the suggestions are only a hint.
A registration form, put together
Everything above, in one place.
<form>
<label for="username">Username</label>
<input type="text" id="username" name="username"
required autocomplete="username" minlength="3">
<label for="email">Email</label>
<input type="email" id="email" name="email"
required autocomplete="email">
<label for="password">Password</label>
<input type="password" id="password" name="password"
required autocomplete="new-password" minlength="8">
<label for="birthdate">Date of birth</label>
<input type="date" id="birthdate" name="birthdate"
required autocomplete="bday" max="2024-12-31">
<label for="phone">Phone</label>
<input type="tel" id="phone" name="phone"
autocomplete="tel" inputmode="tel"
pattern="0\d{1,4}-?\d{1,4}-?\d{4}"
title="e.g. 090-1234-5678">
<button type="submit">Register</button>
</form>
Three points about the type choices:
- Username and password are
textandpassword: express length limits withminlength - Date of birth is
date: narrow the selectable range withmin/max, and read the value as a string - Phone is
tel: formats vary by country, so there is no built-in validation. Supply it withpattern
From here, displaying errors and controlling submission are covered in HTML form validation and submission without a library. The correct autocomplete and inputmode values are in faster mobile forms with inputmode, autocomplete and enterkeyhint.
Summary
patternis for<input>only. Useminlengthon<textarea>type="date"always has aYYYY-MM-DDvalue;valueAsDateis UTC-based and can shift by a daytype="number"is for quantities you increment—not postal codes or phone numbers- Block the scroll wheel on number inputs with
{ passive: false }, and read values withvalueAsNumber(NaNwhen empty) acceptis not validation. Verify on the server.value = ''clears a selection- Pair
type="range"with<output>; update oninput, save onchange - Recolour controls with
accent-color; do not rebuild withappearance: none. Theswitchattribute is Safari-only - Auto-resizing height is
field-sizing: content, withmin-heightandmax-heightwritten together <datalist>accepts values outside the list. Use<select>to enforce- Avoid
monthandweek—Firefox does not support them
One wrong type can produce a field users simply cannot fill in. Asking “is this a value you increment?” and “is the format the same worldwide?” gets you to the right answer most of the time.