Formulir adalah titik temu antara situs dan pengunjungnya: pendaftaran akun, pemesanan tiket, hingga pengisian survei selalu melewati sebuah form. Kesalahan kecil pada struktur form langsung terasa oleh pengunjung — label yang tidak menempel pada kolom membuat pembaca layar kebingungan, sementara isian tanpa validasi mengirim data setengah jadi ke server. Proyek ini melatih kita menyusun formulir pendaftaran acara yang utuh: strukturnya rapi, validasinya berjalan sebelum data dikirim, tampilannya nyaman, dan kesalahannya dijelaskan dengan kalimat sendiri.

Hasil akhir proyek adalah halaman pendaftaran acara fiktif Seminar Web Komunitas dengan fitur berikut:

  • Form pendaftaran lengkap dengan fieldset dan legend: data diri, kategori peserta, dan akun.
  • Label eksplisit for/id pada seluruh kontrol, termasuk select, radio, dan checkbox.
  • Validasi HTML native: required, minlength, pattern, serta rentang tanggal dengan min dan max.
  • Stylesheet form dengan indikator fokus :focus-visible dan tanda ringan untuk isian yang belum sah.
  • Umpan balik JavaScript: pesan kustom per kontrol dari ValidityState dan ringkasan keberhasilan.

Proyek ini menuntut penguasaan beberapa section sebelumnya. Kerjakan bila kita telah melewati: Forms, Form Validation, Accessibility, HTML dan CSS, HTML dan JavaScript, serta Browser dan Web Platform (khususnya chapter validasi browser dan pengiriman form).

Proyek dibagi menjadi empat bagian berurutan:

BagianFokusHasil tahap ini
Bagian 1Struktur formFile formulir.html dengan seluruh kontrol dan label
Bagian 2Validasi HTMLAtribut aturan isian serta autocomplete pada tiap kolom
Bagian 3Styling formFile styles/style.css untuk fokus, jarak, dan tombol
Bagian 4Umpan balik JavaScriptPesan kustom per kontrol dan ringkasan sukses

Tiga folder pendukung menemani keempat bagian. Folder assets/ menyimpan logo-acara.svg, gambar logo yang tampil di atas formulir. Folder styles/ menyimpan style.css yang mulai ditulis pada Bagian 3. Folder tests/ menyimpan cek-struktur.html, halaman checklist mandiri berisi checkbox dengan progres tersimpan otomatis; jalankan setiap kali satu bagian tuntas. Struktur folder proyek tampak seperti berikut:

text
formulir-pendaftaran/
├── bagian-1/
│   └── formulir.html
├── bagian-2/
│   └── formulir-validasi.html
├── bagian-4/
│   ├── formulir-umpan-balik.html
│   └── umpan-balik.js
├── assets/
│   └── logo-acara.svg
├── styles/
│   └── style.css
└── tests/
    └── cek-struktur.html

File contoh pada setiap bagian berada satu tingkat di bawah akar proyek, sehingga rujukan ke aset bersama memakai awalan ../ — misalnya ../styles/style.css dan ../assets/logo-acara.svg. Dengan struktur ini, setiap file contoh dapat diunduh dari halaman bagiannya lalu langsung bekerja tanpa penyesuaian path.

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

styles/style.css

Unduh berkas
css
/* styles/style.css — Seminar Web Komunitas
   Stylesheet bersama proyek Formulir Pendaftaran.
   Ditulis pada Bagian 3, dipakai halaman form dan halaman pemeriksaan. */

/* Token desain: ubah nilai di sini, seluruh halaman ikut menyesuaikan. */
:root {
  --warna-teks: #1f2937;
  --warna-teks-lembut: #5b6472;
  --warna-latar: #f8fafc;
  --warna-aksen: #1a6b54;
  --warna-tepi: #cbd5e1;
  --warna-galat: #b3261e;
  --warna-galat-lembut: #e8b4ae;
  --warna-kartu: #e6f1ec;
}

body {
  margin: 0;
  background-color: var(--warna-latar);
  color: var(--warna-teks);
  font-family: "Segoe UI", Arial, sans-serif;
  line-height: 1.6;
}

main {
  max-width: 34rem;
  margin-inline: auto;
  padding: 1.5rem 1rem 3rem;
}

form {
  display: grid;
  gap: 1.25rem;
}

fieldset {
  margin: 0;
  border: 1px solid var(--warna-tepi);
  border-radius: 8px;
  padding: 1rem;
  display: grid;
  gap: 0.9rem;
}

legend {
  font-weight: 600;
  padding-inline: 0.4rem;
}

form label {
  display: block;
  margin-bottom: 0.35rem;
  font-weight: 600;
}

.kolom {
  width: 100%;
  padding: 0.55rem 0.7rem;
  border: 1px solid var(--warna-tepi);
  border-radius: 6px;
  background-color: #ffffff;
  color: inherit;
  font: inherit;
}

/* Indikator fokus yang terlihat jelas bagi navigasi keyboard. */
.kolom:focus-visible {
  outline: 3px solid var(--warna-aksen);
  outline-offset: 2px;
}

/* Tanda ringan untuk isian yang belum sah — peringatan, bukan hukuman. */
.kolom:invalid {
  border-color: var(--warna-galat-lembut);
}

.pilihan {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-bottom: 0;
  font-weight: normal;
}

.pesan-galat {
  margin: 0;
  color: var(--warna-galat);
  font-size: 0.9rem;
}

button[type="submit"] {
  justify-self: start;
  font: inherit;
  font-weight: 600;
  color: #ffffff;
  background-color: var(--warna-aksen);
  border: 0;
  border-radius: 999px;
  padding: 0.6rem 1.5rem;
  cursor: pointer;
}

button[type="submit"]:focus-visible {
  outline: 3px solid var(--warna-teks);
  outline-offset: 2px;
}

.ringkasan {
  border: 1px solid var(--warna-aksen);
  border-radius: 8px;
  padding: 1rem;
  background-color: var(--warna-kartu);
}

tests/cek-struktur.html

Unduh berkas
html
<!doctype html>
<html lang="id">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Cek Struktur — Proyek Formulir Pendaftaran</title>
  <link rel="stylesheet" href="../styles/style.css">
  <script src="cek-struktur.js" defer></script>
  <style>
    .cek {
      list-style: none;
      padding-left: 0;
    }

    .cek li {
      margin-bottom: 0.5rem;
    }

    .cek input {
      margin-right: 0.5rem;
    }

    #progres {
      font-weight: 700;
    }
  </style>
</head>
<body>
  <main>
    <header>
      <h1>Cek Struktur Proyek Formulir Pendaftaran</h1>
      <p>Halaman ini membantu memeriksa hasil setiap Bagian proyek Seminar Web Komunitas secara mandiri. Centang butir yang terpenuhi; progres tersimpan otomatis di browser dan tidak perlu dicentang ulang.</p>
      <p id="progres">0 dari 16 butir terpenuhi</p>
    </header>

    <fieldset>
      <legend>Struktur form (Bagian 1)</legend>
      <ul class="cek">
        <li><input type="checkbox" id="cek-s1" data-cek="s1"><label for="cek-s1">Form memakai <code>action</code> dan <code>method="post"</code></label></li>
        <li><input type="checkbox" id="cek-s2" data-cek="s2"><label for="cek-s2">Tiga kelompok <code>fieldset</code> masing-masing berjudul lewat <code>legend</code>: Data diri, Kategori peserta, Akun</label></li>
        <li><input type="checkbox" id="cek-s3" data-cek="s3"><label for="cek-s3">Terdapat input <code>text</code>, <code>date</code>, <code>email</code>, <code>password</code>, satu <code>select</code> kota, tiga radio satu grup, satu checkbox, dan tombol submit</label></li>
        <li><input type="checkbox" id="cek-s4" data-cek="s4"><label for="cek-s4">Ketiga radio berbagi <code>name="kategori"</code> dengan <code>value</code> berbeda</label></li>
        <li><input type="checkbox" id="cek-s5" data-cek="s5"><label for="cek-s5">Opsi pertama <code>select</code> bernilai kosong sebagai penanda belum memilih</label></li>
      </ul>
    </fieldset>

    <fieldset>
      <legend>Label dan fieldset (Bagian 1)</legend>
      <ul class="cek">
        <li><input type="checkbox" id="cek-l1" data-cek="l1"><label for="cek-l1">Setiap kontrol punya <code>label</code> dengan pasangan <code>for</code>/<code>id</code> yang cocok</label></li>
        <li><input type="checkbox" id="cek-l2" data-cek="l2"><label for="cek-l2">Mengklik teks label memindahkan fokus ke kolomnya</label></li>
        <li><input type="checkbox" id="cek-l3" data-cek="l3"><label for="cek-l3">Logo <code>assets/logo-acara.svg</code> tampil dengan <code>alt</code> deskriptif</label></li>
      </ul>
    </fieldset>

    <fieldset>
      <legend>Validasi atribut (Bagian 2)</legend>
      <ul class="cek">
        <li><input type="checkbox" id="cek-v1" data-cek="v1"><label for="cek-v1"><code>required</code> terpasang pada semua kolom wajib, termasuk radio pertama dan checkbox persetujuan</label></li>
        <li><input type="checkbox" id="cek-v2" data-cek="v2"><label for="cek-v2"><code>minlength="8"</code> pada kata sandi dan <code>minlength="3"</code> pada nama</label></li>
        <li><input type="checkbox" id="cek-v3" data-cek="v3"><label for="cek-v3"><code>pattern</code> nama menolak angka dan atribut <code>title</code> memberi saran</label></li>
        <li><input type="checkbox" id="cek-v4" data-cek="v4"><label for="cek-v4"><code>min</code> dan <code>max</code> pada tanggal lahir menolak isian di luar rentang</label></li>
        <li><input type="checkbox" id="cek-v5" data-cek="v5"><label for="cek-v5">Token <code>autocomplete</code> terisi: name, bday, address-level2, email, new-password</label></li>
      </ul>
    </fieldset>

    <fieldset>
      <legend>Umpan balik JavaScript (Bagian 3-4)</legend>
      <ul class="cek">
        <li><input type="checkbox" id="cek-j1" data-cek="j1"><label for="cek-j1">Stylesheet <code>../styles/style.css</code> terhubung; cincin fokus <code>:focus-visible</code> terlihat saat menekan Tab</label></li>
        <li><input type="checkbox" id="cek-j2" data-cek="j2"><label for="cek-j2">Form memakai <code>novalidate</code> dan tombol kirim tidak lagi menampilkan gelembung browser</label></li>
        <li><input type="checkbox" id="cek-j3" data-cek="j3"><label for="cek-j3">Kirim formulir kosong: pesan kustom tampil di bawah setiap kolom bermasalah</label></li>
        <li><input type="checkbox" id="cek-j4" data-cek="j4"><label for="cek-j4">Kontrol tidak sah memperoleh <code>aria-invalid="true"</code> di panel inspector</label></li>
        <li><input type="checkbox" id="cek-j5" data-cek="j5"><label for="cek-j5">Isian lengkap dan sah menampilkan kartu ringkasan keberhasilan</label></li>
      </ul>
    </fieldset>

    <p><img src="../assets/logo-acara.svg" alt="" width="72" height="72"></p>
    <p>Halaman pemeriksaan ini juga memakai stylesheet proyek dan gambar aset melalui path relatif <code>../</code>, sekaligus menjadi contoh rujukan lintas folder.</p>
  </main>
</body>
</html>

tests/cek-struktur.js

Unduh berkas
javascript
// cek-struktur.js — checklist mandiri proyek dengan progres tersimpan.
// Digunakan bersama cek-struktur.html pada folder tests/ proyek.

const KUNCI_SIMPAN = "cek:formulir-pendaftaran";

function bacaTersimpan() {
  try {
    return JSON.parse(localStorage.getItem(KUNCI_SIMPAN)) || {};
  } catch (galat) {
    return {};
  }
}

function tulisTersimpan(nilai) {
  try {
    localStorage.setItem(KUNCI_SIMPAN, JSON.stringify(nilai));
  } catch (galat) {
    // Penyimpanan tidak tersedia; checklist tetap berfungsi tanpa persist.
  }
}

function perbaruiProgres() {
  const kotak = Array.from(document.querySelectorAll("[data-cek]"));
  const terpenuhi = kotak.filter(function (item) {
    return item.checked;
  }).length;
  const progres = document.querySelector("#progres");
  if (progres) {
    progres.textContent = terpenuhi + " dari " + kotak.length + " butir terpenuhi";
  }
}

function pasang() {
  const tersimpan = bacaTersimpan();
  const kotak = Array.from(document.querySelectorAll("[data-cek]"));
  kotak.forEach(function (item) {
    item.checked = tersimpan[item.dataset.cek] === true;
    item.addEventListener("change", function () {
      const nilai = bacaTersimpan();
      nilai[item.dataset.cek] = item.checked;
      tulisTersimpan(nilai);
      perbaruiProgres();
    });
  });
  perbaruiProgres();
}

if (document.readyState === "loading") {
  document.addEventListener("DOMContentLoaded", pasang);
} else {
  pasang();
}