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.
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
| 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 |
month / week |
Avoid | Firefox falls back to a plain text field |
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.
<label for="birthday">Date of birth</label>
<input type="date" id="birthday" name="birthday"
min="1900-01-01" max="2024-12-31"
autocomplete="bday">
valueis alwaysYYYY-MM-DD, whatever is displayed. That is what gets submitted and what scripts readminandmaxalso useYYYY-MM-DD- 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());
// stay with the string
const [y, m, day] = input.value.split('-').map(Number);
Make the calendar easier to open
input.addEventListener('click', () => {
if (!('showPicker' in HTMLInputElement.prototype)) return;
try { input.showPicker(); } catch {}
});
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 quantities you want to increment—and 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">
<!-- postal code: use text -->
<label for="zip">Postal code</label>
<input type="text" id="zip" name="zip"
inputmode="numeric" autocomplete="postal-code"
pattern="\d{3}-?\d{4}"
title="Enter as 123-4567 or 1234567">
Disable the wheel to prevent accidental edits.
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.
type=”file”: accept and capture
<!-- 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.
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 displayed.
Sponsored
type=”color” and type=”range”
<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 a range shows no numeric value, always pair it 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 dragging; change fires once on release. Update the display on input, save to the server on change.
Recolour the controls with accent-color—more robust than rebuilding them with appearance: none.
:root { accent-color: #2a7fff; }
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.
To grow the field with its content, CSS field-sizing now handles it.
textarea {
field-sizing: content;
min-height: 4lh;
max-height: 20lh;
width: 100%;
}
A character counter pairs with maxlength.
const ta = document.getElementById('feedback');
const counter = document.getElementById('counter');
ta.addEventListener('input', () => {
counter.textContent = `${ta.value.length} / ${ta.maxLength}`;
});
A registration form, put together
<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">
<button type="submit">Register</button>
</form>
- Sign-up uses
autocomplete="new-password". Usingcurrent-passwordhere confuses password managers <button type="submit">rather than<input type="submit">, so you can put icons or markup insidetype="tel"has no built-in validation—formats vary by country. Addpatternif you need a specific one
Style errors with :user-invalid, not :invalid, or everything turns red on page load.
input:user-invalid {
border-color: #d33;
}
More on this in HTML form features worth using today.
datalist accepts values outside the list
<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>
<datalist> is a suggestion, not a constraint. Values outside the list submit fine. Use <select> to enforce the choices.
- The dropdown cannot be styled with CSS—it is browser UI
- Whether it matches by prefix or substring is browser-dependent
- It becomes unwieldy past a few dozen entries
Attribute-level detail is covered in getting the most out of the HTML input element.
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 } acceptis not validation. Verify on the server.value = ''clears a selection- Pair
type="range"with<output>; update oninput, save onchange <datalist>does not restrict input—use<select>for that- Sign-up passwords use
autocomplete="new-password"
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.