Form dan navigasi adalah tempat orang paling banyak berbuat di halaman: memilih, mengetik, menekan, berpindah. Bayangin formulir kertas di meja resepsionis homestay. Tiap kotak punya tulisan tercetak di sampingnya, tiga pilihan jumlah malam dikurung satu bingkai berjudul "Jumlah malam", catatan kesalahan ditulis tepat di samping kotaknya, dan tamu langganan boleh langsung lompat ke bagian yang dia perlu. Di web, tulisan tercetak itu <label>, bingkainya <fieldset>, catatan kesalahannya aria-describedby, dan jalan pintasnya skip link.
🧪 Tebakan cepat: Di halaman pesan Bu Sri, cincin fokus dihapus dengan
outline: nonesupaya "lebih rapi". Pak Hadi memakai keyboard dan menekan Tab 5 kali dari awal halaman. Fokusnya di mana, dan bagaimana dia tahu? Jawabannya ada di bagian "Fokus yang terlihat, urutan yang masuk akal".
Kita kerjakan form pesan Kamar Teras, yang sama dengan Lab di halaman ini. Semua yang disebut "di pohon Chromium" kami baca dari pohon aksesibilitas Chromium 147 untuk tiap kolom, di tiap keadaan Lab.
Label yang tersambung
<label for="wa">Nomor WhatsApp</label>
<input id="wa" name="wa" type="tel" autocomplete="tel" aria-required="true" aria-invalid="true" aria-describedby="wa-help wa-error">
<p id="wa-help" class="help">Contoh: 0812 3456 7890 atau +62 812 3456 7890</p>
<p id="wa-error" class="error" role="alert">Nomor ini 7 digit. Nomor HP diawali 08 dan panjangnya 10 sampai 13 digit.</p>
for="wa"di label sama persis denganid="wa"di input. Hubungan inilah yang menjadi nama kolom di pohon aksesibilitas, dan membuat klik di tulisan label memindahkan fokus ke kolomnya.- Tanpa label, Chromium memakai placeholder sebagai nama cadangan. Versi awal form Bu Sri cuma punya
placeholder="0812 3456 7890", jadi di pohon Chromium kolom ini bernama "0812 3456 7890": contoh nomor, bukan apa yang diminta. Kolom nama bernama "Hadi Santoso". Placeholder juga hilang dari layar begitu tamu mengetik.
Di latihan nanti, form-nya punya <input type="text" id="name"> dan <input type="email" id="email"> dengan placeholder saja. Tambahkan <label for="name">Nama</label> dan <label for="email">Email</label>; tiap for harus menunjuk id yang benar-benar ada.
Wajib diisi: required atau aria-required
Tanda bintang merah cuma terlihat mata. Kewajibannya diumumkan lewat atribut di input-nya sendiri, dengan salah satu dari dua cara:
| Atribut | Di pohon Chromium | Browser ikut mengecek? |
|---|---|---|
required |
required: true | ya, kecuali form diberi novalidate |
aria-required="true" |
required: true | tidak, pengecekannya kamu tulis sendiri |
Form Bu Sri memeriksa sendiri nomor WhatsApp-nya (pelajaran form design), jadi dia memakai aria-required="true" di Nama lengkap dan Nomor WhatsApp; email opsional gak diberi. Pakai salah satu saja: spesifikasi ARIA in HTML dari W3C meminta aria-required="true" gak dipasang di elemen yang sudah punya required. Di latihan, beri aria-required="true" di input yang wajib diisi.
Pilihan yang berkelompok: fieldset dan legend
<fieldset class="nights">
<legend>Jumlah malam</legend>
<label><input type="radio" name="malam" value="1"> 1 malam</label>
<label><input type="radio" name="malam" value="2" checked> 2 malam</label>
<label><input type="radio" name="malam" value="3"> 3 malam</label>
</fieldset>
- Tanpa
<fieldset>, tiap radio di pohon cumaradio "2 malam". Dengan<fieldset>dan<legend>, radio-radio itu ada di dalamgroup "Jumlah malam", jadi screen reader bisa menyebut untuk apa pilihan itu. name="malam"yang sama menjadikan ketiganya satu grup radio: cuma satu yang bisa terpilih, dan Tab berhenti sekali saja di grup itu, di radio yang terpilih. Panah kanan dan kiri memindahkan pilihan. Kami cek di Chromium, WebKit, dan Firefox: hasilnya sama.- Tombol malam di pelajaran sebelumnya (
<button aria-pressed>) juga boleh. Radio dipakai di sini karena ini bagian dari form yang dikirim.
Error yang terdengar
Kolom yang salah butuh tiga hal (polanya sudah muncul di pelajaran feedback):
aria-invalid="true"menandai kolomnya tidak valid. Pasang cuma saat memang ada error.aria-describedby="wa-help wa-error"menyambungkan contoh dan pesan errornya. Di pohon Chromium, deskripsi kolom ini jadi "Contoh: 0812 3456 7890 atau +62 812 3456 7890 Nomor ini 7 digit. ...", dibacakan setelah namanya saat kolom difokus.role="alert"di paragraf error membuat isinya dibacakan begitu muncul. Paragrafnya ada sejak awal, kosong, lalu diisi saat error terjadi.
Ini memenuhi WCAG 3.3.1 (Error Identification): kolom yang salah ditandai, dan errornya dijelaskan dengan teks.
Skip link
Tiap halaman Bu Sri mulai dengan empat link navigasi. Pengguna keyboard harus melewatinya di setiap halaman: dari awal halaman, Tab ke-5 baru sampai di pilihan malam. Skip link adalah link pertama di halaman yang melompat langsung ke isi utama (WCAG 2.4.1, Bypass Blocks):
<a href="#isi" class="skip-link">Langsung ke isi utama</a>
<nav aria-label="Navigasi utama">...</nav>
<main id="isi" tabindex="-1">...</main>
<style>
.skip-link { position: absolute; left: 8px; top: 8px; padding: 8px 12px; color: #fff; background: #1f2937; transform: translateY(-200%); }
.skip-link:focus { transform: none; }
main:focus { outline: none; }
</style>
href="#isi"menunjukid="isi"yang benar-benar ada. Kalau id-nya gak ada, link-nya gak membawa ke mana-mana.- Link-nya disembunyikan di atas layar dan baru turun saat difokus, jadi gak mengganggu pengguna mouse. Jangan
display: none: elemen seperti itu gak bisa difokus sama sekali. - Kami mengujinya di Chromium, WebKit, dan Firefox: Tab, Enter, lalu Tab sekali lagi sudah sampai di pilihan malam. Tanpa
tabindex="-1", fokusnya tetap di<body>tapi Tab berikutnya tetap mendarat di dalam main. Dengantabindex="-1", fokusnya benar-benar pindah ke main.
Di latihan nanti, tambahkan <a href="#main-content" class="skip-link">Skip to main content</a> paling atas, sebelum <nav>. <main id="main-content"> sudah ada di kode awal; jangan ubah id-nya.
Fokus yang terlihat, urutan yang masuk akal
Urutan fokus mengikuti urutan elemen di HTML (WCAG 2.4.3, Focus Order). Di halaman Bu Sri: Beranda, Kamar, Lokasi, Pesanan, pilihan malam, Nama lengkap, Nomor WhatsApp, Email, tombol Pesan. Jangan memaksakan urutan dengan tabindex positif (tabindex="1" dan seterusnya); susun HTML-nya saja.
Fokus yang terlihat (WCAG 2.4.7, Focus Visible): pengguna keyboard harus selalu tahu di mana fokusnya.
a:focus-visible, input:focus-visible, button:focus-visible {
outline: 3px solid #1f2937;
outline-offset: 2px;
}
:focus-visibleaktif saat browser menilai fokusnya perlu terlihat. Kami cek: di Chromium dan Firefox, Tab selalu memicunya; klik mouse di tombol, link, atau radio gak memicunya; klik di kolom isian memicunya, karena orang perlu tahu di mana kursornya. Safari di Mac malah gak memfokus tombol yang diklik.:focusaktif untuk semua fokus, termasuk klik. Itu sebabnya desainer sering menghapusnya, dan yang hilang justru cincin untuk pengguna keyboard.
Jadi jawaban tebakannya: Tab ke-5 ada di radio "2 malam" (empat link, lalu grup radio yang cuma satu berhenti), tapi Pak Hadi gak bisa tahu, karena gak ada yang tergambar. Itu jawaban tebakannya.
⚠️ Jebakan yang sering ketemu
- Placeholder sebagai satu-satunya nama. Chromium memakainya kalau terpaksa, tapi namanya jadi contoh, dan hilang saat mengetik. Pakai
<label for>. fordanidyang gak sama, atau skip link ke id yang gak ada. Hubungannya putus tanpa pesan apa pun. Latihan mengecek tiapfordanhref="#..."menunjuk id yang ada.requireddanaria-required="true"di elemen yang sama. Pilih satu.- Radio tanpa
<fieldset>dan<legend>. "2 malam" terdengar tanpa konteks. outline: nonetanpa pengganti. Pakai:focus-visibledengan outline sendiri.
TL;DR: Tiap kolom butuh <label for> yang sama persis dengan id-nya; tanpa itu Chromium memakai placeholder sebagai nama ("0812 3456 7890"). Kolom wajib diumumkan dengan required (browser ikut mengecek) atau aria-required="true" (cuma diumumkan), salah satu saja. Pilihan radio dibungkus <fieldset> dengan <legend> ("Jumlah malam"); satu grup radio = satu berhenti Tab, panah memindahkan pilihan. Error: aria-invalid="true", aria-describedby ke contoh dan pesannya, dan role="alert" di paragraf error yang ada sejak awal (WCAG 3.3.1). Skip link <a href="#isi" class="skip-link"> paling atas, menunjuk id main yang ada, tersembunyi sampai difokus (WCAG 2.4.1). Urutan fokus = urutan HTML, tanpa tabindex positif (2.4.3). Fokus harus terlihat (2.4.7): :focus-visible muncul saat Tab dan biasanya gak saat klik mouse, jadi jangan hapus outline tanpa pengganti.