This episode breaks down sophisticated form validation: built-in attributes like required and pattern, modern input types plus inputmode, and the Constraint Validation API for JavaScript-based validation with custom messages.

Validation is no longer an exclusive JavaScript job. Modern browsers have a built-in validation system that activates just by writing attributes on input elements — fast, consistent, and recognized by screen readers. Episode 17 covers advanced forms and browser validation: attributes like required and pattern, modern input types, and the Constraint Validation API for finer control.
Client-side validation speeds up feedback, but it's not a replacement for server-side validation. Combining both — browser validation for convenience, server validation for security — is the correct pattern you'll always use in real projects.
The most basic attribute is required, which forces users to fill in a field. Pair it with minlength and maxlength to control text length:
<form action="/daftar" method="post">
<label for="nama">Nama</label>
<input type="text" id="nama" name="nama" required minlength="3" maxlength="50">
<button type="submit">Kirim</button>
</form>When the field is empty, the browser shows a built-in message and stops submission. maxlength even prevents typing past the limit — you can't type more than 50 characters.
For number inputs, min, max, and step constrain the range of accepted values:
<label for="usia">Usia</label>
<input type="number" id="usia" name="usia" min="17" max="65" step="1">The form won't submit if the number is outside 17 to 65. These attributes also give hints to the up/down arrow controls the browser provides.
pattern accepts a regular expression and fits specific formats like phone numbers:
<label for="hp">Nomor HP</label>
<input type="tel" id="hp" name="hp" pattern="[0-9]{10,13}" placeholder="081234567890">The regex pattern above means ten to thirteen digits. Test the pattern with a regex tool before using it, because the built-in error message doesn't explain the correct format.
type="email", type="url", and type="tel" make the browser show the appropriate virtual keyboard on mobile devices — letters for email, a special pad for web addresses.
<label for="email">Email</label>
<input type="email" id="email" name="email" required>
<label for="kode">Kode OTP</label>
<input type="text" id="kode" name="kode" inputmode="numeric" maxlength="6">The inputmode="numeric" attribute brings up a numeric keypad for an OTP code without changing the type to number. Use inputmode when the format is short text filled with digits.
Sometimes a form is validated through JavaScript, for example when submitting with fetch. The Constraint Validation API provides the methods:
const form = document.getElementById("form-daftar");
form.addEventListener("submit", (event) => {
if (!form.checkValidity()) {
event.preventDefault();
form.reportValidity();
return;
}
fetch("/daftar", { method: "POST", body: new FormData(form) });
});form.checkValidity() returns true or false. form.reportValidity() displays the built-in error message on the problematic field. This pair keeps feedback consistent with browser validation.
Built-in messages are in English and sometimes too generic. Replace them with clearer language via setCustomValidity:
const kataSandi = document.getElementById("kata-sandi");
kataSandi.addEventListener("input", () => {
if (kataSandi.validity.patternMismatch) {
kataSandi.setCustomValidity("Kata sandi minimal 8 karakter.");
} else {
kataSandi.setCustomValidity("");
}
});As long as setCustomValidity holds non-empty text, the field is considered invalid. Clear it again with setCustomValidity("") once the problem is gone.
Without JavaScript, CSS gives visual cues through the :valid and :invalid pseudo-classes:
input:invalid {
border-color: #dc2626;
}
input:valid {
border-color: #16a34a;
}These styles appear immediately, even when the page first loads. To show them only after the user tries to submit, add a class like was-validated on the form at submit time via JavaScript.
Assemble everything into one page:
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Pendaftaran Kursus</title>
</head>
<body>
<h1>Pendaftaran Kursus</h1>
<form id="form-daftar" action="/daftar" method="post" novalidate>
<label for="nama">Nama lengkap</label>
<input type="text" id="nama" name="nama" required minlength="3">
<label for="email">Email</label>
<input type="email" id="email" name="email" required>
<label for="usia">Usia</label>
<input type="number" id="usia" name="usia" min="17" max="65">
<label for="hp">Nomor HP</label>
<input type="tel" id="hp" name="hp" pattern="[0-9]{10,13}">
<button type="submit">Daftar Sekarang</button>
</form>
<script src="js/validasi.js"></script>
</body>
</html>Notice the novalidate attribute on the form: without it, the browser validates before the submit event fires and JavaScript never gets its turn. With novalidate, validation control is fully in the script's hands.
A too-strict pattern often rejects input that's actually valid, like spaces and hyphens in phone numbers. If the real format is flexible, loosen the pattern or clean the input on the server.
Browser validation can be bypassed easily with developer tools. Always re-validate on the server — data from the client can never be trusted blindly.
Episode 17 refines your forms: required, min, max, and pattern attributes, modern input types with inputmode, and the Constraint Validation API for JavaScript-based validation with custom messages.
Key takeaways:
pattern uses regex for special formats like phone numbers.inputmode triggers the right keyboard on mobile devices.checkValidity and reportValidity work as a pair via JavaScript.setCustomValidity replaces error messages with clearer language.In the next episode, episode 18, we'll cover HTML templates and basic shadow DOM — the template element for storing unrendered markup and how to wrap components in a shadow DOM so their styles and structure are isolated.