Feedback adalah cara halaman memberi tahu apa yang sedang terjadi, setiap saat: sedang memuat, kosong, gagal, berhasil. State (keadaan) adalah tiap kemungkinan wujud halaman itu. Bayangin memesan di warung. Kamu bilang "nasi goreng satu", dan Bu warung menjawab "siap, ditunggu ya". Kamu melihat wajannya mulai berasap, lalu piringnya datang. Kalau nasinya habis, dia bilang "maaf, nasinya habis, mie goreng mau?". Kalau dia diam saja setelah kamu memesan, kamu gak tahu pesananmu didengar, dan kamu memesan sekali lagi.
🧪 Tebakan cepat: Pak Hadi menekan "Pesan kamar, bayar di tempat". Tombolnya cuma berubah abu-abu, tanpa tulisan apa pun, dan 3 detik kemudian muncul "Pesanan terkirim". Halamannya bekerja. Apa yang salah? Jawabannya ada di bagian "Tombol Pesan saat menunggu".
Enam keadaan yang harus didesain
Desainer sering cuma menggambar halaman yang berhasil dan penuh data. Padahal tiap bagian halaman punya lima keadaan dasar, ditambah satu untuk koneksi. Ini semuanya di halaman pesan Homestay Kabut:
| Keadaan | Kapan | Di halaman Bu Sri |
|---|---|---|
| Loading | menunggu data atau jawaban | daftar kamar: skeleton; tombol Pesan: "Memesan..." |
| Empty | datanya memang gak ada | "Gak ada kamar kosong 12 Des." dengan tombol "Lihat 13 Des" |
| Error | permintaan gagal | "Pesanan belum terkirim. Coba lagi." Pilihan malam tetap seperti tadi |
| Success | permintaan berhasil | "Pesanan terkirim. Bu Sri akan konfirmasi lewat WhatsApp." |
| Partial | sebagian berhasil | foto kamar gagal dimuat, tapi harga dan tombol Pesan tetap jalan |
| Offline | gak ada koneksi | "Kamu sedang offline. Pesananmu belum terkirim." |
Lima yang pertama (empty, loading, error, success, partial) adalah state yang harus didesain untuk tiap elemen yang memuat data. Offline adalah error khusus yang pantas punya pesannya sendiri, karena penyebab dan jalan keluarnya beda: bukan "coba lagi", tapi "sambungkan internet dulu".
Pesan yang baik menyebut apa yang terjadi dan apa langkah berikutnya. "Error 500" gak membantu siapa pun; "Pesanan belum terkirim. Coba lagi." membantu, apalagi dengan tombol "Coba lagi" di sampingnya.
Berapa lama menunggu, indikator apa
Jakob Nielsen (Usability Engineering, 1993) memberi tiga batas waktu yang masih dipakai sampai sekarang: 0,1 detik terasa instan, 1 detik masih menjaga alur pikiran, 10 detik adalah batas orang mau tetap fokus menunggu. Dari situ, Nielsen Norman Group (Katie Sherwin, 2014) menyarankan:
| Lama menunggu | Indikator |
|---|---|
| di bawah 1 detik | gak perlu indikator |
| 2 sampai 10 detik | animasi berulang: spinner atau skeleton |
| lebih dari 10 detik | progress bar yang menunjukkan persen selesai |
Untuk 2 sampai 10 detik, pilihannya tergantung apa yang dimuat (NN/g, Samhita Tankala, 2023):
- Skeleton screen: kotak abu-abu seukuran isi yang akan datang (foto, nama kamar, harga). Untuk memuat satu halaman atau satu daftar utuh, skeleton lebih baik daripada spinner: orang sudah melihat bentuk halamannya, dan isinya muncul tepat di tempat yang sama, tanpa halaman meloncat.
- Spinner: lingkaran berputar, untuk satu bagian kecil atau satu aksi, misalnya tombol Pesan yang menunggu jawaban server.
Tombol Pesan saat menunggu
<button class="book" id="book">Pesan kamar, bayar di tempat</button>
<p id="status" role="status"></p>
<p id="error" class="error" role="alert"></p>
<script>
const book = document.querySelector('#book');
book.addEventListener('click', async () => {
if (book.getAttribute('aria-disabled') === 'true') return; // cegah pesan dua kali
book.setAttribute('aria-disabled', 'true');
book.textContent = 'Memesan...';
try {
await kirimPesanan(); // fetch ke server Bu Sri
document.querySelector('#status').textContent = 'Pesanan terkirim. Bu Sri akan konfirmasi lewat WhatsApp.';
} catch {
document.querySelector('#error').textContent = 'Pesanan belum terkirim. Coba lagi.';
} finally {
book.setAttribute('aria-disabled', 'false');
book.textContent = 'Pesan kamar, bayar di tempat';
}
});
</script>
textContent = 'Memesan...'mengubah tulisannya, jadi tamu tahu ketukannya diterima dan sedang diproses.aria-disabled="true"menandai tombol sedang gak bisa dipakai, dan barisreturndi awal mengabaikan ketukan kedua, jadi pesanan gak terkirim dua kali.- Kenapa bukan atribut
disabled? Kami mengujinya di Chrome, Safari, dan Firefox: tombol yang sedang fokus lalu diberidisabledkehilangan fokus, dan fokusnya jatuh ke<body>. Pengguna keyboard kehilangan tempatnya. Denganaria-disabled, fokus tetap di tombol. finallyjalan setelah berhasil maupun gagal, jadi tombolnya selalu kembali normal.
Jadi jawaban tebakannya: gak ada loading feedback. Tombol abu-abu tanpa tulisan bisa berarti "sedang diproses", bisa juga "tombolnya mati". Pak Hadi gak tahu ketukannya terdaftar, dan 3 detik itu ada di zona yang butuh indikator. Itu jawaban tebakannya.
aria-live: kabar yang dibacakan
Pengguna screen reader gak melihat tulisan "Pesanan terkirim" muncul. Live region membuat perubahan teks di dalamnya dibacakan otomatis, dan WCAG 4.1.3 (Status Messages, level AA) memintanya untuk pesan seperti ini:
role="status"(sama denganaria-live="polite"): dibacakan setelah screen reader selesai bicara. Untuk kabar sukses.role="alert"(sama denganaria-live="assertive"): memotong dan dibacakan saat itu juga. Untuk error yang harus segera ditangani.
Taruh live region-nya, kosong, sejak halaman dimuat, lalu isi teksnya saat ada kabar. Screen reader memantau region yang sudah ada; region yang ditambahkan bersamaan dengan teksnya sering gak dibacakan.
Untuk kolom isian yang salah, polanya sama, ditambah dua atribut:
<label for="hp">Nomor HP</label>
<input id="hp" class="input-error" aria-invalid="true" aria-describedby="hp-error">
<span id="hp-error" class="error-msg" role="alert">Nomor HP harus diawali 08.</span>
<style>
.input-error { border: 1px solid #EF4444; } /* 3,76:1, cukup untuk garis (minimal 3:1) */
.error-msg { color: #b91c1c; } /* 6,47:1, cukup untuk teks (minimal 4,5:1) */
</style>
aria-invalid="true"menandai isian yang salah; screen reader menyebut kolomnya tidak valid ("invalid").aria-describedby="hp-error"menghubungkan kolom keidpesannya, jadi pesannya ikut dibacakan saat kolom difokus.border: 1px solid #EF4444membuatborder-colorkolomnya#EF4444, tanda merah yang terlihat. Teks pesannya pakai merah yang lebih gelap, karena#EF4444cuma 3,76:1.
⚠️ Jebakan yang sering ketemu
- Cuma mendesain keadaan sukses. Pengguna pertama Bu Sri justru melihat empty state, dan koneksi di Dieng bisa putus.
- Tombol yang cuma jadi abu-abu. Gak jelas sedang memproses atau mati, dan atribut
disabledmembuang fokus keyboard. Ubah tulisannya, pakaiaria-disabled. - Spinner untuk satu halaman penuh, atau skeleton untuk satu tombol. Skeleton untuk memuat halaman atau daftar, spinner untuk satu aksi atau satu bagian kecil.
- Error yang menghapus isian. Pilihan malam dan nama tamu harus tetap ada setelah gagal.
role="alert"untuk semua pesan. Alert memotong apa pun yang sedang dibacakan. Kabar sukses cukuprole="status".
TL;DR: Desain enam keadaan, bukan cuma sukses: loading, empty, error, success, partial (lima state dasar), plus offline. Batas waktu Nielsen: 0,1 detik terasa instan, 1 detik menjaga alur, 10 detik batas fokus; di atas 1 detik beri indikator, spinner atau skeleton untuk 2 sampai 10 detik, progress bar di atas 10 detik. Skeleton screen lebih baik untuk memuat halaman atau daftar; spinner untuk satu aksi. Tombol yang menunggu mengubah tulisannya ("Memesan...") dan memakai aria-disabled, bukan disabled yang membuang fokus. Kabar sukses di role="status", error di role="alert" (WCAG 4.1.3), region-nya ada sejak awal. Kolom yang salah: aria-invalid="true", aria-describedby ke id pesannya, border-color: #EF4444, dan teks pesan #b91c1c.