Chapter pertama section ini menunjukkan bahwa buka-tutup konten bisa dibangun dengan HTML murni. Bila kebutuhannya tumbuh — animasi halus, banyak panel yang saling terkait, atau struktur markup tertentu — kita memerlukan pola yang lebih umum. Pola ini dikenal sebagai disclosure pattern dan didokumentasikan oleh WAI-ARIA Authoring Practices Guide (APG). Chapter ini membedah anatomi polanya, lalu menempatkan versi native dan versi custom berdampingan agar perbedaannya terlihat jelas.

Anatomi Pola Disclosure #

Disclosure adalah pola UI untuk membuka dan menutup sebuah region konten: satu kontrol pemicu, satu status, dan satu area yang muncul serta hilang. Menurut WAI-ARIA APG, pemicunya adalah button yang membawa aria-expanded, dan regionnya adalah elemen dengan id yang dirujuk lewat aria-controls. Karena pemicunya tombol asli, keyboard menangani Enter dan Space secara otomatis tanpa kode tambahan. Ketiga bagian itu dirangkum sebagai berikut:

  • Kontrol: button dengan label yang mendeskripsikan isi regionnya.
  • Status: aria-expanded="true" atau "false" pada tombol, diumumkan pembaca layar sebagai dibentangkan atau diciutkan.
  • Region: elemen konten dengan id, dirujuk aria-controls, dan disembunyikan lewat atribut hidden.

Versi Native: details dan summary #

Pasangan details dan summary adalah implementasi disclosure pattern yang seluruh aturannya ditangani browser: status, klik, keyboard, dan pengumuman pembaca layar hadir tanpa satu atribut ARIA pun. Versi native menjadi pilihan pertama selama konten boleh menjadi anak langsung dari details. Batasannya ada di fleksibilitas: animasi pembukaan terbatas, styling strukturnya kaku, dan panel tidak bisa diletakkan terpisah dari pemicunya.

Versi Custom: Tombol, aria-expanded, dan Region #

Versi custom menukar keringanan itu dengan kendali. Kita memilihnya ketika konten perlu beranimasi lewat transisi CSS, ketika beberapa panel harus tersinkron seperti accordion, atau ketika struktur markup menuntut pemicu dan panel berjauhan. Harganya jelas: atribut hidden, aria-expanded, dan aria-controls harus dipasang serta diperbarui sendiri oleh JavaScript. Lupa memperbarui salah satunya membuat tombol mengaku keadaan yang salah.

Aspekdetails dan summaryTombol + ARIA + JavaScript
Klik dan keyboardOtomatisOtomatis (tombol asli)
Status diumumkanOtomatisPerlu aria-expanded
Menyembunyikan regionOtomatisPerlu atribut hidden
Animasi pembukaanTerbatasBebas lewat transisi CSS
Penempatan panelHarus di dalam detailsBebas di mana pun

Contoh: Dua Versi Berdampingan #

Kita tulis kedua versi untuk konten yang sama agar perbedaannya terlihat langsung. Halaman bantuan berikut menyajikan versi native di kartu pertama dan versi custom di kartu kedua.

<!doctype html>
<html lang="id">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Disclosure: Native dan Custom</title>
  <style>
    body {
      font-family: sans-serif;
      max-width: 560px;
      margin: 24px auto;
      line-height: 1.6;
    }
    .kartu {
      border: 1px solid #d1d5db;
      border-radius: 8px;
      padding: 12px 16px;
      margin-bottom: 16px;
    }
    summary {
      cursor: pointer;
      font-weight: bold;
    }
    button {
      padding: 6px 12px;
      border-radius: 6px;
      border: 1px solid #6b7280;
      background: #f3f4f6;
      cursor: pointer;
    }
  </style>
</head>
<body>
  <h1>Cara Pakai Kupon</h1>

  <div class="kartu">
    <details>
      <summary>Versi native: details dan summary</summary>
      <p>Masukkan kode kupon di halaman pembayaran, lalu tekan Terapkan.</p>
    </details>
  </div>

  <div class="kartu">
    <button id="tombol-kupon" type="button" aria-expanded="false" aria-controls="panel-kupon">
      Versi custom: tombol dan panel
    </button>
    <p id="panel-kupon" hidden>
      Masukkan kode kupon di halaman pembayaran, lalu tekan Terapkan.
    </p>
  </div>

  <script>
    const tombol = document.querySelector("#tombol-kupon");
    const panel = document.querySelector("#panel-kupon");

    function alihkanPanel() {
      if (panel.hidden) {
        panel.hidden = false;
        tombol.setAttribute("aria-expanded", "true");
      } else {
        panel.hidden = true;
        tombol.setAttribute("aria-expanded", "false");
      }
    }

    tombol.addEventListener("click", alihkanPanel);
  </script>
</body>
</html>

Output yang Dihasilkan:

text
Saat halaman baru dibuka:
  - Kartu pertama menampilkan label "Versi native: details dan summary" dalam keadaan tertutup
  - Kartu kedua menampilkan tombol "Versi custom: tombol dan panel" dengan panel tersembunyi
Salah satu label atau tombol ditekan:
  - Kartu yang bersangkutan membuka paragraf "Masukkan kode kupon..."
  - Tombol versi custom memperbarui aria-expanded menjadi true
Ditekan lagi:
  - Keduanya menutup kembali dan aria-expanded kembali ke false

Mari kita bedah isi halaman itu:

  1. Kartu pertama (details): seluruh mekanisme berjalan tanpa atribut ARIA dan tanpa JavaScript.
  2. <button id="tombol-kupon">: pemicu versi custom; membawa type="button", aria-expanded="false" sebagai status awal, dan aria-controls="panel-kupon" yang menunjuk id regionnya.
  3. <p id="panel-kupon" hidden>: region yang dirujuk; atribut hidden mengeluarkannya dari render sekaligus dari accessibility tree.
  4. Function alihkanPanel: cabang if membaca panel.hidden, lalu menulis keadaan panel dan status tombol di tempat yang sama agar keduanya sinkron.
  5. addEventListener("click", alihkanPanel): menghubungkan tombol dengan fungsi; penekanan Enter maupun Space pada tombol memicu event click yang sama.

Perhatikan bahwa kedua versi menghasilkan perilaku yang sama; yang berbeda adalah siapa yang menangani detailnya. Mulailah dari versi native, lalu naik ke versi custom hanya ketika kebutuhan animasi atau sinkronisasi benar-benar muncul. Chapter berikutnya berpindah ke dua elemen yang menyampaikan status berupa angka, yaitu progress dan meter.

Referensi resmi untuk pendalaman:

Berkas Sumber

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

disclosure-dua-versi.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>Disclosure: Native dan Custom</title>
  <style>
    body {
      font-family: sans-serif;
      max-width: 560px;
      margin: 24px auto;
      line-height: 1.6;
    }
    .kartu {
      border: 1px solid #d1d5db;
      border-radius: 8px;
      padding: 12px 16px;
      margin-bottom: 16px;
    }
    summary {
      cursor: pointer;
      font-weight: bold;
    }
    button {
      padding: 6px 12px;
      border-radius: 6px;
      border: 1px solid #6b7280;
      background: #f3f4f6;
      cursor: pointer;
    }
  </style>
</head>
<body>
  <h1>Cara Pakai Kupon</h1>

  <div class="kartu">
    <details>
      <summary>Versi native: details dan summary</summary>
      <p>Masukkan kode kupon di halaman pembayaran, lalu tekan Terapkan.</p>
    </details>
  </div>

  <div class="kartu">
    <button id="tombol-kupon" type="button" aria-expanded="false" aria-controls="panel-kupon">
      Versi custom: tombol dan panel
    </button>
    <p id="panel-kupon" hidden>
      Masukkan kode kupon di halaman pembayaran, lalu tekan Terapkan.
    </p>
  </div>

  <script>
    const tombol = document.querySelector("#tombol-kupon");
    const panel = document.querySelector("#panel-kupon");

    function alihkanPanel() {
      if (panel.hidden) {
        panel.hidden = false;
        tombol.setAttribute("aria-expanded", "true");
      } else {
        panel.hidden = true;
        tombol.setAttribute("aria-expanded", "false");
      }
    }

    tombol.addEventListener("click", alihkanPanel);
  </script>
</body>
</html>