What HTML forms can do without JavaScript
Forms collect JavaScript remarkably quickly. Email validation gets a library, numeric limits get a custom handler, and a mobile keyboard problem gets another workaround.
A developer needs email validation, so a library appears. A field needs a minimum value, so a custom script is added. Mobile users need a better keyboard, so another workaround is built.
Modern HTML already handles many of these cases.
That does not mean you never need JavaScript in a form. Complex forms still need custom logic. But it is worth learning what the browser can do before rebuilding it yourself.
Start with the value the field accepts
This works:
<input type="text">
But it tells the browser almost nothing.
For email:
<input type="email">
For URLs:
<input type="url">
For phone numbers:
<input type="tel">
For search:
<input type="search">
For numbers:
<input type="number">
The correct type can improve validation, mobile keyboards, and browser behaviour.
Required fields
<input
type="email"
name="email"
required
>
The browser can stop the form from being submitted when the field is empty.
Minimum and maximum values
For numbers:
<input
type="number"
min="1"
max="10"
>
For dates:
<input
type="date"
min="2026-01-01"
max="2026-12-31"
>
For text length:
<input
type="text"
minlength="3"
maxlength="50"
>
These basic rules do not need a validation library.
Step values
<input
type="number"
min="0"
step="0.5"
>
Or:
<input
type="number"
min="0"
step="0.01"
>
Pattern matching
For some text fields, you can use pattern:
<input
type="text"
pattern="[A-Z]{2}[0-9]{4}"
>
That example expects two capital letters followed by four digits.
Do not turn pattern into a huge unreadable regular expression if a simpler form design would work better.
Useful autocomplete
The autocomplete attribute helps browsers fill forms correctly.
<input
name="name"
autocomplete="name"
>
<input
type="email"
name="email"
autocomplete="email"
>
<input
name="street"
autocomplete="street-address"
>
<input
name="postal"
autocomplete="postal-code"
>
This can make checkout and contact forms much faster to complete.
One-time codes
<input
type="text"
inputmode="numeric"
autocomplete="one-time-code"
>
On supported phones, the operating system may offer a received verification code automatically.
Better mobile keyboards
Use inputmode when the expected keyboard is different from the data type.
<input
type="text"
inputmode="numeric"
>
This is useful for codes made only from digits.
Remember that inputmode does not validate the value. It only helps the browser choose a keyboard.
Labels matter
Always connect a visible label to its field.
<label for="email">
Email address
</label>
<input
id="email"
name="email"
type="email"
>
Or wrap the field:
<label>
Email address
<input name="email" type="email">
</label>
Do not use placeholder text as the only label.
Help text
If a field needs more explanation:
<label for="password">
Password
</label>
<input
id="password"
type="password"
aria-describedby="password-help"
>
<p id="password-help">
Use at least 12 characters.
</p>
Now assistive technology can connect the help text to the field.
Native validation states
CSS can style validation states.
input:user-invalid {
border-color: red;
}
input:user-valid {
border-color: green;
}
I prefer :user-invalid over :invalid in many forms because it avoids showing an error before the user has interacted with the field.
Do not rely only on color
A red border alone is not a complete error message.
Users should also get clear text explaining what went wrong.
For more complex validation, JavaScript can still be useful for better messages.
Group related controls
Use <fieldset> and <legend> for related radio buttons or checkboxes.
<fieldset>
<legend>Preferred contact method</legend>
<label>
<input type="radio" name="contact" value="email">
Email
</label>
<label>
<input type="radio" name="contact" value="phone">
Phone
</label>
</fieldset>
This gives the group a clear name.
A simple contact form
<form action="/contact" method="post">
<label for="name">
Name
</label>
<input
id="name"
name="name"
autocomplete="name"
required
>
<label for="email">
Email
</label>
<input
id="email"
name="email"
type="email"
autocomplete="email"
required
>
<label for="message">
Message
</label>
<textarea
id="message"
name="message"
minlength="10"
required
></textarea>
<button type="submit">
Send message
</button>
</form>
This already has useful labels, autocomplete, email validation, required fields, and a minimum message length.
No JavaScript is needed for those basics.
Server-side validation still matters
Browser validation helps the user, but never trust it as your only validation.
A user can bypass client-side checks.
Your server should still validate required values, formats, limits, permissions, and anything that affects security or stored data.
Start with HTML
My preferred order is:
HTML input type
↓
HTML validation attributes
↓
autocomplete / inputmode
↓
clear labels and help text
↓
CSS states
↓
JavaScript only for rules HTML cannot handle
That usually leads to a smaller and more reliable form.
HTML forms are not perfect, and server-side validation is still required. Even so, I would exhaust the native attributes before recreating their behaviour in JavaScript.
Enjoyed reading this?
If you found this article helpful, consider supporting my writing. Every little bit helps keep this blog going.
Related articles
- Choosing Between srcset and picture
Choose srcset when the image stays the same and picture when the crop, composition, or format needs to change.
- Finding Hidden Content With hidden="until-found"
Let Find in Page and fragment navigation reveal collapsed content with hidden=until-found, while keeping a usable fallback.
- HTML Features That Can Save You Some JavaScript
Use native disclosures, dialogs, popovers, form attributes, and responsive images before rebuilding their basic behaviour.