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
fieldsetdanlegend: data diri, kategori peserta, dan akun. - Label eksplisit
for/idpada seluruh kontrol, termasukselect, radio, dan checkbox. - Validasi HTML native:
required,minlength,pattern, serta rentang tanggal denganmindanmax. - Stylesheet form dengan indikator fokus
:focus-visibledan tanda ringan untuk isian yang belum sah. - Umpan balik JavaScript: pesan kustom per kontrol dari
ValidityStatedan 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:
| Bagian | Fokus | Hasil tahap ini |
|---|---|---|
| Bagian 1 | Struktur form | File formulir.html dengan seluruh kontrol dan label |
| Bagian 2 | Validasi HTML | Atribut aturan isian serta autocomplete pada tiap kolom |
| Bagian 3 | Styling form | File styles/style.css untuk fokus, jarak, dan tombol |
| Bagian 4 | Umpan balik JavaScript | Pesan 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:
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.htmlFile 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.
Tahap 1 dari proyek Formulir Pendaftaran.
Buka → # Bagian 2: Validasi HTMLTahap 2 dari proyek Formulir Pendaftaran.
Buka → # Bagian 3: Styling FormTahap 3 dari proyek Formulir Pendaftaran.
Buka → # Bagian 4: Umpan Balik JavaScriptTahap 4 dari proyek Formulir Pendaftaran.
Buka →Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
styles/style.css
Unduh berkas/* 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<!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// 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();
}