Membuat Accordion Kustom Responsif dengan HTML, CSS, & Vanilla JavaScript

Membuat Accordion Kustom Responsif dengan HTML, CSS, & Vanilla JavaScript

Membuat Accordion Kustom Responsif dengan HTML, CSS, & Vanilla JavaScript

Novian Hidayat
2025-05-13

Pelajari cara membangun komponen accordion yang interaktif dan hemat ruang dari awal. Panduan ini mencakup struktur HTML, styling CSS untuk panel yang bisa dibuka-tutup, serta logika JavaScript murni untuk fungsionalitas toggle dan pertimbangan aksesibilitas.

Membangun Accordion Kustom yang Dinamis dan Aksesibel dengan HTML, CSS, dan Vanilla JavaScript

Selamat datang di tutorial pembuatan accordion kustom! Accordion adalah pola antarmuka pengguna (UI) yang sangat berguna untuk menampilkan sejumlah besar informasi dalam ruang terbatas. Setiap item accordion memiliki header yang bisa diklik untuk menampilkan atau menyembunyikan panel konten terkait. Ini sering digunakan untuk FAQ, daftar fitur, atau navigasi bertingkat.

Mengapa Membuat Accordion Kustom?

  • Kontrol Penuh: Desain dan perilaku sepenuhnya sesuai keinginan Anda.
  • Pembelajaran Fundamental: Memperkuat pemahaman tentang manipulasi DOM, event handling, dan transisi CSS.
  • Aksesibilitas: Kesempatan untuk membangun komponen yang ramah bagi semua pengguna, termasuk mereka yang menggunakan teknologi pendukung.
  • Ringan: Tidak ada kode berlebih dari plugin atau library.

Apa yang Akan Kita Buat?

Kita akan membuat komponen accordion dengan fitur berikut:

  1. Struktur HTML yang semantik.
  2. Item Accordion yang terdiri dari header (tombol) dan panel konten.
  3. Fungsionalitas Toggle: Mengklik header akan membuka atau menutup panel kontennya.
  4. Opsi Perilaku:
    • Hanya satu panel yang bisa terbuka dalam satu waktu (opsional).
    • Beberapa panel bisa terbuka bersamaan (default).
  5. Indikator Visual: Menampilkan ikon (misalnya, panah) yang berubah saat panel terbuka/tertutup.
  6. Transisi CSS yang mulus untuk efek membuka/menutup panel.
  7. Logika JavaScript murni untuk mengelola fungsionalitas.
  8. Pertimbangan Aksesibilitas Dasar: Atribut ARIA yang sesuai.

Prasyarat

  • Text Editor: VS Code, Sublime Text, dll.
  • Web Browser: Chrome, Firefox, dll.
  • Pemahaman Dasar HTML dan CSS.
  • Pengetahuan Dasar JavaScript: Variabel, fungsi, event listener, manipulasi DOM (kelas, atribut, style).
  • (Opsional) Ikon: Font Awesome untuk ikon panah.

Mari kita mulai!


Langkah 1: Persiapan Proyek dan Struktur Folder

  1. Buat folder utama proyek, misalnya proyek-accordion-kustom.
  2. Di dalamnya, buat:
    • index.html
    • style.css
    • script.js

Struktur dasar:

proyek-accordion-kustom/
├── index.html
├── style.css
└── script.js

Langkah 2: Struktur Dasar HTML (index.html)

Buka index.html. Kita akan membuat daftar item accordion.

<!DOCTYPE html>
<html lang="id">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Accordion Kustom - Vanilla JS</title>
    <link rel="stylesheet" href="style.css">
    <!-- Font Awesome untuk ikon (opsional) -->
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
</head>
<body>

    <header>
        <h1>Contoh Accordion Kustom</h1>
    </header>

    <main>
        <p>Klik pada judul di bawah untuk melihat atau menyembunyikan kontennya.</p>

        <div class="accordion-container">
            <div class="accordion-item">
                <button class="accordion-header" aria-expanded="false" aria-controls="panel1">
                    Pertanyaan Umum 1: Apa itu Produk X?
                    <i class="fas fa-chevron-down accordion-icon"></i>
                </button>
                <div class="accordion-panel" id="panel1" role="region" aria-labelledby="accordion-header-for-panel1" hidden>
                    <p>Produk X adalah solusi inovatif yang dirancang untuk membantu Anda menyelesaikan masalah Y dengan cara yang efisien dan efektif. Kami menggunakan teknologi terkini untuk memberikan pengalaman terbaik.</p>
                </div>
            </div>

            <div class="accordion-item">
                <button class="accordion-header" aria-expanded="false" aria-controls="panel2">
                    Pertanyaan Umum 2: Bagaimana cara mendaftar?
                    <i class="fas fa-chevron-down accordion-icon"></i>
                </button>
                <div class="accordion-panel" id="panel2" role="region" aria-labelledby="accordion-header-for-panel2" hidden>
                    <p>Proses pendaftaran sangat mudah. Kunjungi halaman pendaftaran kami, isi formulir dengan data yang diperlukan, dan ikuti instruksi selanjutnya. Anda akan segera bisa menggunakan layanan kami.</p>
                    <ul>
                        <li>Langkah 1: Kunjungi halaman registrasi.</li>
                        <li>Langkah 2: Isi data diri.</li>
                        <li>Langkah 3: Verifikasi email Anda.</li>
                    </ul>
                </div>
            </div>

            <div class="accordion-item">
                <button class="accordion-header" aria-expanded="false" aria-controls="panel3">
                    Pertanyaan Umum 3: Apakah ada biaya tersembunyi?
                    <i class="fas fa-chevron-down accordion-icon"></i>
                </button>
                <div class="accordion-panel" id="panel3" role="region" aria-labelledby="accordion-header-for-panel3" hidden>
                    <p>Kami percaya pada transparansi. Semua biaya terkait layanan kami akan dijelaskan secara rinci sebelum Anda melakukan komitmen. Tidak ada biaya tersembunyi yang perlu Anda khawatirkan.</p>
                </div>
            </div>
        </div>
        <!-- Akhir accordion-container -->

    </main>

    <footer>
        <p>&copy; 2025 Halaman Accordion Kustom</p>
    </footer>

    <script src="script.js"></script>
</body>
</html>

Penjelasan HTML:

  • .accordion-container: Wrapper untuk semua item accordion.
  • .accordion-item: Setiap unit accordion tunggal.
  • .accordion-header: Ini adalah elemen <button> yang akan diklik pengguna. Menggunakan <button> adalah praktik terbaik untuk aksesibilitas karena sudah memiliki fungsionalitas keyboard bawaan.
    • aria-expanded="false": Menunjukkan bahwa panel terkait saat ini tertutup. Akan diubah oleh JS.
    • aria-controls="panel1": Menghubungkan tombol ini dengan panel konten yang dikontrolnya (yang memiliki id="panel1").
    • <i class="fas fa-chevron-down accordion-icon"></i>: Ikon panah (opsional). Kelas accordion-icon untuk styling.
  • .accordion-panel: Kontainer untuk konten yang bisa dibuka/tutup.
    • id="panel1": ID unik yang dirujuk oleh aria-controls.
    • role="region": Menunjukkan bahwa ini adalah area penting di halaman.
    • aria-labelledby="accordion-header-for-panel1": (Perlu ditambahkan ID pada button header juga, misal <button id="accordion-header-for-panel1">) Menghubungkan panel dengan header yang melabelinya. Untuk implementasi lebih sederhana, kita bisa mengabaikan aria-labelledby ini jika aria-controls sudah cukup jelas, atau jika konten header sudah cukup deskriptif. Untuk tutorial ini, kita akan fokus pada aria-expanded dan aria-controls.
    • hidden: Atribut HTML5 untuk menyembunyikan panel secara default.

Perbaikan Aksesibilitas pada HTML (opsional tapi direkomendasikan): Agar aria-labelledby pada panel berfungsi dengan baik, setiap .accordion-header (button) juga memerlukan ID unik. Contoh:

<button class="accordion-header" id="header1" aria-expanded="false" aria-controls="panel1">
    Pertanyaan Umum 1: Apa itu Produk X?
    <i class="fas fa-chevron-down accordion-icon"></i>
</button>
<div class="accordion-panel" id="panel1" role="region" aria-labelledby="header1" hidden>
    ...
</div>

Kita akan menggunakan struktur HTML awal untuk menjaga kesederhanaan tutorial, namun perbaikan ini baik untuk diketahui.


Langkah 3: Styling Dasar CSS (style.css)

Buka style.css. Kita akan mengatur tampilan accordion, header, dan panel.

/* style.css */
body {
    margin: 0;
    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
    line-height: 1.6;
    color: #333;
    background-color: #f4f7f6;
}

header, main, footer {
    padding: 20px;
    max-width: 700px;
    margin: 0 auto;
}
main {
    background-color: #fff;
    padding: 30px;
    border-radius: 8px;
    box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}

/* Styling Accordion */
.accordion-container {
    border: 1px solid #ddd;
    border-radius: 5px;
    overflow: hidden; /* Untuk rounded corners yang rapi */
}

.accordion-item {
    border-bottom: 1px solid #ddd;
}
.accordion-item:last-child {
    border-bottom: none;
}

.accordion-header {
    background-color: #f9f9f9;
    color: #333;
    cursor: pointer;
    padding: 15px 20px;
    width: 100%;
    text-align: left;
    border: none;
    outline: none; /* Hapus outline default browser pada focus */
    font-size: 1.1rem;
    font-weight: 500;
    display: flex; /* Untuk menata teks dan ikon */
    justify-content: space-between; /* Teks di kiri, ikon di kanan */
    align-items: center;
    transition: background-color 0.3s ease;
}

.accordion-header:hover,
.accordion-header.is-open { /* Atau :focus-visible untuk styling focus yang lebih baik */
    background-color: #e9e9e9;
}

.accordion-header:focus-visible { /* Styling khusus untuk fokus keyboard */
    box-shadow: 0 0 0 2px #007bff; /* Contoh outline biru */
    z-index: 1; /* Pastikan outline terlihat */
    position: relative; /* Agar z-index bekerja */
}


.accordion-icon {
    font-size: 0.9em;
    transition: transform 0.3s ease-out;
    color: #555;
}

.accordion-header.is-open .accordion-icon {
    transform: rotate(180deg); /* Putar ikon panah saat terbuka */
}

.accordion-panel {
    background-color: white;
    padding: 0px 20px; /* Padding atas/bawah akan dianimasi dengan max-height */
    max-height: 0; /* Awalnya tinggi 0, disembunyikan */
    overflow: hidden; /* Sembunyikan konten yang meluber */
    transition: max-height 0.4s ease-out, padding 0.4s ease-out; /* Animasi tinggi dan padding */
    border-top: 1px dashed #eee; /* Garis pemisah halus */
}

/* Kelas ini akan ditambahkan oleh JS untuk panel yang terbuka */
.accordion-panel.is-open {
    /* max-height akan diatur oleh JS berdasarkan scrollHeight */
    /* padding: 20px; akan di-enable juga */
}

.accordion-panel p,
.accordion-panel ul {
    margin-top: 0;
    margin-bottom: 15px;
    font-size: 0.95rem;
    color: #555;
}
.accordion-panel ul {
    padding-left: 20px;
}
.accordion-panel p:last-child,
.accordion-panel ul:last-child {
    margin-bottom: 0;
}

Penjelasan CSS Awal:

  • .accordion-header: Dibuat agar terlihat seperti tombol yang bisa diklik. display: flex digunakan untuk menata teks dan ikon.
  • .accordion-header.is-open: Kelas yang akan ditambahkan oleh JS saat header aktif (panelnya terbuka).
  • .accordion-icon: Styling untuk ikon panah. Transisi transform akan menganimasikan putaran ikon.
  • .accordion-panel:
    • max-height: 0 dan overflow: hidden adalah kunci untuk menyembunyikan panel dan menganimasikan tinggi.
    • padding: 0px 20px;: Padding atas dan bawah diatur ke 0 saat tertutup, dan akan diubah saat terbuka untuk efek transisi yang lebih halus.
    • Transisi untuk max-height dan padding akan membuat efek buka/tutup yang mulus.
  • Kelas .is-open pada .accordion-panel akan digunakan oleh JS untuk mengatur max-height ke nilai sebenarnya (scrollHeight) dan mengembalikan padding.

Langkah 4: JavaScript - Logika Membuka dan Menutup Panel

Buka script.js. Kita akan menulis logika untuk mengontrol accordion.

// script.js
document.addEventListener('DOMContentLoaded', () => {
    const accordionItems = document.querySelectorAll('.accordion-item');

    // Opsi: Apakah hanya satu panel yang boleh terbuka dalam satu waktu?
    const allowMultipleOpen = true; // Set ke false jika hanya satu yang boleh terbuka

    accordionItems.forEach(item => {
        const header = item.querySelector('.accordion-header');
        const panel = item.querySelector('.accordion-panel');

        if (!header || !panel) return; // Pastikan elemen ada

        header.addEventListener('click', () => {
            const isOpen = header.classList.contains('is-open');

            // Jika tidak boleh ada multiple open, tutup semua yang lain dulu
            if (!allowMultipleOpen && !isOpen) {
                closeAllOtherPanels(item);
            }

            // Toggle panel saat ini
            if (isOpen) {
                // Tutup panel
                header.classList.remove('is-open');
                header.setAttribute('aria-expanded', 'false');
                panel.style.maxHeight = null; // Kembali ke CSS default (0)
                panel.style.paddingTop = '0px';
                panel.style.paddingBottom = '0px';
                panel.setAttribute('hidden', ''); // Tambahkan hidden saat animasi selesai
                panel.addEventListener('transitionend', function handler() {
                    if (!header.classList.contains('is-open')) { // Cek lagi kalau-kalau dibuka lagi cepat
                        panel.setAttribute('hidden', '');
                    }
                    panel.removeEventListener('transitionend', handler);
                }, { once: true });
            } else {
                // Buka panel
                panel.removeAttribute('hidden'); // Hapus hidden SEBELUM mengukur scrollHeight
                header.classList.add('is-open');
                header.setAttribute('aria-expanded', 'true');
                // Set max-height agar transisi CSS bekerja
                // Perlu sedikit delay agar 'hidden' terhapus dan scrollHeight bisa diukur
                requestAnimationFrame(() => {
                    panel.style.paddingTop = '20px'; // Sesuaikan dengan padding di CSS
                    panel.style.paddingBottom = '20px';
                    panel.style.maxHeight = panel.scrollHeight + 'px';
                });
            }
        });

        // Set initial state based on aria-expanded (jika ada yang terbuka by default)
        // atau pastikan panel tertutup sesuai 'hidden'
        if (header.getAttribute('aria-expanded') === 'true') {
            panel.removeAttribute('hidden');
            header.classList.add('is-open');
            panel.style.paddingTop = '20px';
            panel.style.paddingBottom = '20px';
            panel.style.maxHeight = panel.scrollHeight + 'px';
        } else {
            panel.style.maxHeight = null;
            panel.style.paddingTop = '0px';
            panel.style.paddingBottom = '0px';
            panel.setAttribute('hidden', '');
        }
    });

    function closeAllOtherPanels(currentItem) {
        accordionItems.forEach(item => {
            if (item !== currentItem) {
                const header = item.querySelector('.accordion-header');
                const panel = item.querySelector('.accordion-panel');
                if (header && panel && header.classList.contains('is-open')) {
                    header.classList.remove('is-open');
                    header.setAttribute('aria-expanded', 'false');
                    panel.style.maxHeight = null;
                    panel.style.paddingTop = '0px';
                    panel.style.paddingBottom = '0px';
                    panel.setAttribute('hidden', ''); // Tutup langsung atau dengan transisi
                }
            }
        });
    }
});

Penjelasan JavaScript:

  1. Seleksi Elemen: Mengambil semua .accordion-item.
  2. allowMultipleOpen: Variabel konfigurasi. Jika false, membuka satu panel akan menutup panel lain yang sedang terbuka.
  3. Looping Melalui Item: Untuk setiap item accordion:
    • Ambil header dan panelnya.
    • Tambahkan event listener click pada header.
  4. Logika Klik Header:
    • Cek apakah panel saat ini sedang terbuka (isOpen).
    • Jika !allowMultipleOpen dan panel akan dibuka: Panggil closeAllOtherPanels() untuk menutup panel lain.
    • Toggle Panel Saat Ini:
      • Jika Terbuka (ingin menutup):
        • Hapus kelas .is-open dari header.
        • Set aria-expanded ke false.
        • Set panel.style.maxHeight = null; (ini akan membuat CSS mengambil max-height: 0; dari stylesheet).
        • Set padding atas/bawah panel ke 0px.
        • Tambahkan atribut hidden setelah transisi selesai agar panel tidak bisa di-tab.
      • Jika Tertutup (ingin membuka):
        • Hapus atribut hidden dari panel sebelum mengukur scrollHeight.
        • Tambahkan kelas .is-open ke header.
        • Set aria-expanded ke true.
        • Menggunakan requestAnimationFrame untuk memastikan browser punya waktu memproses penghapusan hidden sebelum kita mengukur scrollHeight dan menerapkan style.
        • Set panel.style.paddingTop dan panel.style.paddingBottom ke nilai yang diinginkan (misal, 20px).
        • Set panel.style.maxHeight = panel.scrollHeight + 'px';. scrollHeight adalah tinggi total konten di dalam panel. Ini penting agar transisi CSS max-height bekerja dengan benar.
  5. closeAllOtherPanels(currentItem): Fungsi untuk menutup semua panel kecuali panel yang sedang di-klik (jika allowMultipleOpen adalah false).
  6. Inisialisasi State Awal: Saat halaman dimuat, kita periksa atribut aria-expanded pada setiap header. Jika ada yang true (mungkin diset di HTML untuk terbuka secara default), kita buka panel tersebut. Jika tidak, pastikan semua panel tertutup dengan benar.

Langkah 5: Menguji Accordion Anda

Simpan semua file (index.html, style.css, script.js). Buka index.html di browser Anda.

  • Klik pada header accordion. Panel konten di bawahnya seharusnya membuka/menutup dengan mulus.
  • Ikon panah seharusnya berputar.
  • Jika Anda set allowMultipleOpen = false;, membuka satu panel seharusnya menutup panel lain yang terbuka. Jika true, beberapa panel bisa terbuka bersamaan.
  • Periksa atribut aria-expanded di DevTools; nilainya harus berubah sesuai.
  • Panel yang tertutup seharusnya tidak bisa di-tab (karena atribut hidden).

Langkah 6: Pertimbangan Tambahan & Peningkatan

  • Aksesibilitas Keyboard:
    • Pastikan header (yang merupakan <button>) bisa difokuskan dan diaktifkan dengan Enter atau Space. Ini sudah bawaan untuk elemen <button>.
    • Navigasi antar header accordion bisa dilakukan dengan tombol Tab.
    • (Lebih lanjut) Anda bisa menambahkan navigasi dengan tombol panah Atas/Bawah antar header accordion, tapi ini sudah di luar cakupan tutorial dasar.
  • Indikator Fokus yang Jelas: CSS :focus-visible sudah ditambahkan untuk styling fokus keyboard yang lebih baik pada header.
  • Menyimpan State: Jika ingin state accordion (panel mana yang terbuka) diingat saat halaman di-refresh, Anda bisa menggunakan localStorage.
  • Panel Terbuka Secara Default: Anda bisa mengatur satu atau lebih panel terbuka secara default dengan menambahkan kelas .is-open pada header dan panel terkait di HTML, dan mengatur aria-expanded="true" pada header. JavaScript kita sudah menangani inisialisasi ini.
  • Animasi Padding: Transisi padding ditambahkan agar konten tidak langsung muncul/hilang saat max-height berubah, memberikan efek yang lebih halus.

Kesimpulan

Selamat! Anda telah berhasil membuat komponen accordion kustom yang fungsional, responsif, dan mempertimbangkan aksesibilitas dasar. Anda sekarang memiliki pemahaman yang baik tentang cara kerja salah satu pola UI yang paling umum.

Apa yang telah dipelajari:

  • Struktur HTML semantik untuk accordion dengan atribut ARIA.
  • Styling CSS untuk header dan panel yang bisa di-toggle, termasuk transisi max-height.
  • Logika JavaScript untuk mengelola state buka/tutup panel.
  • Opsi untuk mengizinkan satu atau beberapa panel terbuka.
  • Pentingnya scrollHeight untuk animasi tinggi.

Langkah Selanjutnya:

  • Membuat Komponen Tabs: Dengan pemahaman dari accordion, membuat tabs akan menjadi langkah berikutnya yang logis. Konsepnya mirip (menampilkan satu panel konten pada satu waktu), tetapi interaksinya berbeda.
  • Kustomisasi Lanjutan: Eksplorasi efek animasi yang berbeda, atau tambahkan fitur seperti pencarian di dalam konten accordion.
  • Integrasi dengan Data Dinamis: Membangun accordion yang itemnya diambil dari data JavaScript atau API.

Teruslah bereksperimen dan membangun! Semakin banyak komponen kustom yang Anda buat, semakin kuat pemahaman Anda tentang pengembangan web front-end.

Tutorial Terkait