Atribut Validasi

Validasi bawaan browser dengan required, pattern, min, max, dan maxlength.

Daftar isi

Validasi bawaan browser memeriksa isi formulir saat pengiriman dan menahannya bila ada aturan yang dilanggar, tanpa satu baris JavaScript pun. Mekanisme ini disebut constraint validation, yaitu pemeriksaan kendala yang ditetapkan melalui atribut seperti required, pattern, min, max, dan maxlength, ditambah pemeriksaan format dari tipe input. Topik ini membahas atribut-atribut tersebut, pesan error bawaan beserta pseudo-kelas CSS :invalid, penonaktifan lewat novalidate, serta demo interaktif yang memperlihatkan semuanya bekerja. Setelah membacanya, aturan dasar formulir dapat ditegakkan langsung oleh browser, dan skrip tambahan hanya diperlukan untuk aturan yang lebih khusus.

Cara validasi bawaan berjalan#

Pemeriksaan berlangsung pada saat formulir dikirim, bukan setiap kali pengguna mengetik. Browser menelusuri kontrol-kontrol di dalam formulir dan mencocokkan nilainya dengan seluruh kendala yang terpasang. Bila ada pelanggaran, pengiriman dihentikan, pesan error ditampilkan di dekat kontrol yang bermasalah, dan fokus dipindahkan ke kontrol pertama yang melanggar. Perilaku ini membuat umpan baliknya konsisten di semua situs, karena pengguna sudah mengenal bentuk pesan yang sama dari formulir lain.

Pemeriksaan ini terjadi sebelum event submit, sehingga skrip yang menangani pengiriman hanya perlu mengurus data yang sudah lolos pemeriksaan. Karena itu pendekatan yang tepat adalah menyatakan sebanyak mungkin aturan melalui atribut, dan menyisakan skrip untuk aturan yang memang tidak dapat dinyatakan secara deklaratif, misalnya kesesuaian dua kolom yang saling bergantung. Validasi sisi server tetap diperlukan, dan alasannya dibahas pada bagian kesalahan umum.

required dan tipe sebagai pemeriksaan pertama#

Atribut required menandai kontrol yang tidak boleh kosong saat dikirim, dan pemeriksaan ini berlaku pada input, select, serta textarea. Pada kelompok tombol radio, satu atribut required saja pada salah satu anggota sudah menuntut pilihan pada seluruh kelompoknya. Kontrol kosong yang wajib diisi memicu pesan error bawaan, dan pengiriman ditahan sampai kolomnya terisi.

Pemeriksaan tipe berjalan sebelum atribut lain, karena tipe menetapkan format dasar nilai. Tipe email menuntut bentuk alamat yang wajar, tipe url menuntut teks dengan skema, dan tipe number menuntut angka yang sah. Pelanggaran formatnya memicu jenis error tersendiri, yaitu ketidaksesuaian tipe, sehingga pesannya berbeda dari kolom kosong. Dua pemeriksaan dasar ini sering menutup separuh kebutuhan validasi sebuah formulir tanpa atribut tambahan apa pun.

pattern untuk format khusus#

Atribut pattern menetapkan pola menggunakan sintaks ekspresi reguler JavaScript, dan nilai kontrolnya harus cocok dengan pola itu secara utuh. Browser memperlakukan pola seolah diapit ^(?: dan )$, sehingga tanda awal dan akhir pola tidak perlu ditulis sendiri. Pola yang sering digunakan antara lain [0-9]{5} untuk lima digit dan [A-Za-z]{3,10} untuk tiga sampai sepuluh huruf.

html Contoh: pattern lima digit
<p>
  <label for="kartu">Nomor kartu pelajar (5 digit)</label>
  <input id="kartu" name="kartu" type="text"
         required pattern="[0-9]{5}" inputmode="numeric">
</p>

Kontrol dengan pattern sebaiknya diberi teks bantuan yang menjelaskan format yang diharapkan, misalnya melalui label atau teks di sekitarnya, karena pesan error bawaan bersifat umum dan tidak mendeskripsikan aturan pola secara spesifik. Atribut title pernah menjadi cara menambahkan keterangan, tetapi isinya hanya tampil dengan cara yang tidak dapat diandalkan, sehingga penjelasan yang terlihat di halaman tetap menjadi pilihan utama. Kombinasi inputmode="numeric" pada contoh juga membantu, karena papan ketik sentuhnya menonjolkan angka.

Batas panjang dan batas nilai#

Atribut maxlength membatasi jumlah karakter yang dapat diketik. Kontrol berhenti menerima masukan begitu batas tercapai, sehingga pelanggaran tidak pernah terjadi melalui pengetikan biasa. Sebaliknya, minlength tidak dapat membatasi pengetikan, karena kolom yang baru terisi setengah memang belum berpelanggaran apa pun, dan pemeriksaannya baru terjadi saat pengiriman. Keduanya sering dipasangkan, misalnya minlength="3" dan maxlength="40" pada kolom nama.

AtributKontrol yang tepatSaat diperiksa
maxlengthkolom tekssaat mengetik, masukan berhenti
minlengthkolom tekssaat pengiriman
min dan maxnumber dan datesaat pengiriman
stepnumber dan datesaat pengiriman

Atribut min, max, dan step membatasi nilai angka maupun tanggal, dan pelanggarannya muncul sebagai di bawah batas, di atas batas, atau bukan kelipatan yang diizinkan. Pada tipe date, nilai batasnya ditulis YYYY-MM-DD, sedangkan pada number nilainya berupa angka. Seluruh batas ini bekerja sama dengan tipe kontrolnya, sehingga menaruhnya pada tipe yang salah tidak memberikan pemeriksaan apa pun. Karena maxlength menghentikan pengetikan, pasangan yang seimbang untuk kolom nama adalah maxlength yang menjaga batas atas secara nyata dan minlength yang menjaga batas bawahnya saat pengiriman.

:invalid, novalidate, dan pesan error#

Pseudo-kelas :invalid menandai kontrol yang sedang melanggar kendalanya, sehingga gaya CSS dapat menonjolkan kolom yang bermasalah. Penggunaannya perlu kehati-hatian, karena kontrol wajib yang masih kosong sejak halaman dimuat juga dihitung tidak sah, sehingga halaman yang baru terbuka langsung memperlihatkan kolom berbingkai merah. Pseudo-kelas :user-invalid mempersempitnya, yaitu baru aktif setelah pengguna menyentuh kontrol tersebut, dan dukungannya tersedia pada browser modern terkini. Sampai dukungan itu merata, kombinasi yang lazim adalah gaya ringan untuk :invalid dan pesan yang menjelaskan kondisi kolom kosong wajib diisi.

Atribut novalidate pada elemen form mematikan seluruh pemeriksaan bawaan, sehingga pengiriman tetap berlangsung meskipun ada pelanggaran. Atribut ini digunakan saat aplikasi mengganti pemeriksaan dengan skripnya sendiri, tetapi tetap ingin membaca status kendala lewat properti validity dan metode checkValidity(). Pesan bawaan setiap kontrol juga dapat dibaca lewat properti validationMessage, yang berguna untuk menyalin pesan resmi browser ke laporan buatan sendiri.

Demo interaktif validasi bawaan#

Demo berikut memuat empat kolom dengan kendala berbeda, yaitu panjang teks, tipe surel, pola lima digit, dan rentang tahun. Kirim formulir dalam keadaan kosong, dan browser menampilkan pesan error bawaan pada kolom pertama yang bermasalah. Isi formulir dengan benar untuk melihat laporan valid yang tercetak di bawahnya. Aktifkan sakelar novalidate, lalu kirim ulang dalam keadaan salah. Kali ini pengiriman berjalan meskipun kendalanya dilanggar, sementara laporan di bawahnya memperlihatkan status setiap kolom.

Kode sumber demo
<!DOCTYPE html>
<html lang="id">
<head>
  <meta charset="utf-8">
  <title>Demo validasi bawaan browser</title>
  <link rel="stylesheet" href="index.css">
</head>
<body>
  <h1>Pendaftaran anggota perpustakaan</h1>
  <p>
    <label>
      <input type="checkbox" id="saklar-novalidate">
      Nonaktifkan validasi bawaan (novalidate)
    </label>
  </p>

  <form id="pendaftaran" action="/daftar" method="post">
    <p>
      <label for="nama">Nama lengkap (3 sampai 40 karakter)</label>
      <input id="nama" name="nama" type="text" required minlength="3" maxlength="40">
    </p>
    <p>
      <label for="surel">Surel</label>
      <input id="surel" name="surel" type="email" required>
    </p>
    <p>
      <label for="kartu">Nomor kartu pelajar (5 digit)</label>
      <input id="kartu" name="kartu" type="text" required pattern="[0-9]{5}" inputmode="numeric">
    </p>
    <p>
      <label for="tahun">Tahun masuk (2010 sampai 2026)</label>
      <input id="tahun" name="tahun" type="number" required min="2010" max="2026" step="1">
    </p>
    <p>
      <button type="submit">Kirim</button>
      <button type="button" id="bersihkan">Bersihkan laporan</button>
    </p>
  </form>

  <div id="laporan" aria-live="polite"></div>
  <script src="index.js"></script>

<style>
body {
  font-family: sans-serif;
  max-width: 34rem;
  margin: 0 auto;
  padding: 0 1rem;
  color: #1f2937;
}

form {
  border-top: 1px solid #cbd5e1;
  padding-top: 0.5rem;
}

p {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  margin: 0.5rem 0;
}

input {
  padding: 0.3rem 0.5rem;
  font-size: 1rem;
  border: 1px solid #94a3b8;
  border-radius: 4px;
}

input:invalid {
  border-color: #b91c1c;
}

#laporan {
  white-space: pre-line;
  margin-top: 1rem;
  padding: 0.75rem;
  background: #f8fafc;
  border: 1px solid #cbd5e1;
  border-radius: 4px;
  min-height: 2rem;
}

</style>
<style>
body {
  font-family: sans-serif;
  max-width: 34rem;
  margin: 0 auto;
  padding: 0 1rem;
  color: #1f2937;
}

form {
  border-top: 1px solid #cbd5e1;
  padding-top: 0.5rem;
}

p {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  margin: 0.5rem 0;
}

input {
  padding: 0.3rem 0.5rem;
  font-size: 1rem;
  border: 1px solid #94a3b8;
  border-radius: 4px;
}

input:invalid {
  border-color: #b91c1c;
}

#laporan {
  white-space: pre-line;
  margin-top: 1rem;
  padding: 0.75rem;
  background: #f8fafc;
  border: 1px solid #cbd5e1;
  border-radius: 4px;
  min-height: 2rem;
}

</style>
<script>
const formulir = document.querySelector("#pendaftaran");
const laporan = document.querySelector("#laporan");
const saklar = document.querySelector("#saklar-novalidate");

saklar.addEventListener("change", () => {
  formulir.noValidate = saklar.checked;
});

function ringkasStatus(field) {
  const s = field.validity;
  if (s.valid) return "valid";
  if (s.valueMissing) return "kosong, wajib diisi";
  if (s.typeMismatch) return "format tidak sesuai tipenya";
  if (s.patternMismatch) return "tidak sesuai pola";
  if (s.tooShort) return `terlalu pendek, minimal ${field.minLength} karakter`;
  if (s.tooLong) return `terlalu panjang, maksimal ${field.maxLength} karakter`;
  if (s.rangeUnderflow) return `di bawah minimum ${field.min}`;
  if (s.rangeOverflow) return `di atas maksimum ${field.max}`;
  if (s.stepMismatch) return "bukan kelipatan step";
  return field.validationMessage;
}

function laporkan() {
  const baris = [];
  for (const field of formulir.elements) {
    if (field.tagName === "BUTTON") continue;
    baris.push(`${field.name}: ${ringkasStatus(field)}`);
  }
  laporan.textContent = baris.join("\n");
}

formulir.addEventListener("submit", (event) => {
  event.preventDefault();
  laporkan();
});

document.querySelector("#bersihkan").addEventListener("click", () => {
  laporan.textContent = "";
});

</script>
<script>
const formulir = document.querySelector("#pendaftaran");
const laporan = document.querySelector("#laporan");
const saklar = document.querySelector("#saklar-novalidate");

saklar.addEventListener("change", () => {
  formulir.noValidate = saklar.checked;
});

function ringkasStatus(field) {
  const s = field.validity;
  if (s.valid) return "valid";
  if (s.valueMissing) return "kosong, wajib diisi";
  if (s.typeMismatch) return "format tidak sesuai tipenya";
  if (s.patternMismatch) return "tidak sesuai pola";
  if (s.tooShort) return `terlalu pendek, minimal ${field.minLength} karakter`;
  if (s.tooLong) return `terlalu panjang, maksimal ${field.maxLength} karakter`;
  if (s.rangeUnderflow) return `di bawah minimum ${field.min}`;
  if (s.rangeOverflow) return `di atas maksimum ${field.max}`;
  if (s.stepMismatch) return "bukan kelipatan step";
  return field.validationMessage;
}

function laporkan() {
  const baris = [];
  for (const field of formulir.elements) {
    if (field.tagName === "BUTTON") continue;
    baris.push(`${field.name}: ${ringkasStatus(field)}`);
  }
  laporan.textContent = baris.join("\n");
}

formulir.addEventListener("submit", (event) => {
  event.preventDefault();
  laporkan();
});

document.querySelector("#bersihkan").addEventListener("click", () => {
  laporan.textContent = "";
});

</script></body>
</html>

Bingkai merah yang tampak sejak awal berasal dari :invalid pada kolom wajib yang masih kosong, dan hal itu adalah pelajaran visualnya, yaitu :invalid mengikuti keadaan kendala, bukan riwayat penyuntingan. Laporan di bagian bawah demo dibaca dari properti validity setiap kontrol, sehingga jenis pelanggaran yang tampil persis sama dengan yang diperiksa browser.

Meniru aturan validasi dalam skrip#

Urutan pemeriksaan pada browser dapat ditiru dengan skrip biasa untuk memahami prioritasnya. Fungsi berikut memeriksa kendala satu per satu, dimulai dari required, lalu panjang teks, pola, dan batas nilai, sesuai urutan yang lazim pada formulir nyata.

js
// Validasi sederhana yang meniru perilaku atribut validasi browser
function validasi(field, nilai) {
  const teks = String(nilai);
  if (field.required && teks === "") {
    return "wajib diisi";
  }
  if (teks === "") {
    return "valid";
  }
  if (field.minlength && teks.length < field.minlength) {
    return `terlalu pendek, minimal ${field.minlength} karakter`;
  }
  if (field.maxlength && teks.length > field.maxlength) {
    return `terlalu panjang, maksimal ${field.maxlength} karakter`;
  }
  if (field.pattern && !new RegExp(`^(?:${field.pattern})$`).test(teks)) {
    return `tidak sesuai pola ${field.pattern}`;
  }
  if (field.type === "number") {
    const angka = Number(teks);
    if (field.min !== undefined && angka < field.min) {
      return `di bawah nilai minimum ${field.min}`;
    }
    if (field.max !== undefined && angka > field.max) {
      return `di atas nilai maksimum ${field.max}`;
    }
  }
  return "valid";
}

const formulir = [
  { name: "nama", required: true, minlength: 3, maxlength: 40 },
  { name: "kartu", required: true, pattern: "[0-9]{5}" },
  { name: "tahun", type: "number", required: true, min: 2010, max: 2026 },
];

const masukan = { nama: "Di", kartu: "12a45", tahun: 2030 };

for (const field of formulir) {
  console.log(`${field.name}: ${validasi(field, masukan[field.name])}`);
}
text Keluaran
nama: terlalu pendek, minimal 3 karakter
kartu: tidak sesuai pola [0-9]{5}
tahun: di atas nilai maksimum 2026

Tiga kolom pada contoh semuanya melanggar, dan setiap pelanggarannya berbeda jenis, yaitu panjang minimum, pola, dan batas atas. Fungsi ini tetap dimulai dari required karena pemeriksaan kolom kosong harus menang lebih dahulu, dan baris if (teks === "") setelahnya menjaga aturan format tidak memeriksa nilai kosong dua kali. Nilai valid pada keluaran menandakan kontrol lolos seluruh kendala yang terpasang, dan urutan pemeriksaan inilah yang dianut pemeriksaan bawaan browser pada kasus serupa.

Berkas utuh formulir tervalidasi#

Berkas berikut mengumpulkan seluruh atribut topik ini dalam satu formulir pendaftaran ujian, yaitu required, minlength, maxlength, pattern, min, max, step, ditambah gaya :invalid pada kolomnya. Formulir ini tidak menggunakan skrip sama sekali, sehingga seluruh pemeriksaannya murni pekerjaan browser.

html validasi.html
<!DOCTYPE html>
<html lang="id">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Contoh atribut validasi</title>
  <style>
    body { font-family: sans-serif; max-width: 34rem; margin: 0 auto; padding: 0 1rem; }
    p { display: flex; flex-direction: column; gap: 0.25rem; }
    input { padding: 0.3rem 0.5rem; font-size: 1rem; }
    input:invalid { border-color: #b91c1c; }
  </style>
</head>
<body>
  <h1>Pendaftaran ujian lab</h1>

  <form action="/ujian" method="post">
    <p>
      <label for="peserta">Nama peserta (3 sampai 40 karakter)</label>
      <input id="peserta" name="peserta" type="text" required minlength="3" maxlength="40">
    </p>
    <p>
      <label for="surel">Surel</label>
      <input id="surel" name="surel" type="email" required>
    </p>
    <p>
      <label for="kartu">Nomor kartu peserta (5 digit)</label>
      <input id="kartu" name="kartu" type="text" required pattern="[0-9]{5}" inputmode="numeric">
    </p>
    <p>
      <label for="skor">Skor harapan (0 sampai 100, kelipatan 5)</label>
      <input id="skor" name="skor" type="number" min="0" max="100" step="5">
    </p>
    <p><button type="submit">Daftar</button></p>
  </form>
</body>
</html>

Buka berkasnya di browser, isi kolom skor dengan angka 7 lalu kirim, dan pesan error bawaan menuntut kelipatan lima. Mencoba kombinasi pelanggaran lain, seperti nama satu huruf atau kartu berisi huruf, memperlihatkan urutan pemeriksaan yang sama seperti pada simulasi skrip di atas.

Kesalahan umum#

  • Menganggap validasi bawaan sebagai pengganti pemeriksaan server. Pemeriksaan di browser dapat dilewati, misalnya lewat permintaan yang dibuat skrip, sehingga data berbahaya tetap bisa tiba. Validasi server tetap menjadi benteng terakhir.
  • Mengandalkan penjelasan format pada atribut title. Isinya tidak tampil pada perangkat sentuh dan tidak selalu dibacakan, sehingga aturan polanya dijelaskan pada label atau teks di sekitar kolom.
  • Menggunakan maxlength lalu menyangka data panjang tidak mungkin tiba di server. Batas itu hanya berlaku pada pengetikan di kolomnya, dan nilai lain tetap dapat dikirim dari luar formulir.
  • Menonaktifkan validasi dengan novalidate tanpa menyediakan skrip pengganti. Pengiriman bermasalah pun lolos tanpa pemeriksaan apa pun, sehingga atribut ini hanya untuk aplikasi yang benar-benar mengganti pemeriksaannya sendiri.

Ringkasan#

  • Validasi bawaan berjalan saat pengiriman, menahan formulir, menampilkan pesan error, dan memindahkan fokus ke kontrol pertama yang melanggar.
  • required dan tipe input menangani pemeriksaan dasar, sedangkan pattern menambahkan aturan format dengan ekspresi reguler yang dicocokkan secara utuh.
  • min, max, dan step membatasi nilai angka dan tanggal, sedangkan maxlength menghentikan pengetikan dan minlength diperiksa saat pengiriman.
  • :invalid menata kontrol yang melanggar kendala, dan novalidate mematikan seluruh pemeriksaan bawaan untuk aplikasi yang memvalidasi lewat skrip.

Referensi#