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:
- Struktur HTML yang semantik.
- Item Accordion yang terdiri dari header (tombol) dan panel konten.
- Fungsionalitas Toggle: Mengklik header akan membuka atau menutup panel kontennya.
- Opsi Perilaku:
- Hanya satu panel yang bisa terbuka dalam satu waktu (opsional).
- Beberapa panel bisa terbuka bersamaan (default).
- Indikator Visual: Menampilkan ikon (misalnya, panah) yang berubah saat panel terbuka/tertutup.
- Transisi CSS yang mulus untuk efek membuka/menutup panel.
- Logika JavaScript murni untuk mengelola fungsionalitas.
- 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
- Buat folder utama proyek, misalnya
proyek-accordion-kustom
. - 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>© 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 memilikiid="panel1"
).<i class="fas fa-chevron-down accordion-icon"></i>
: Ikon panah (opsional). Kelasaccordion-icon
untuk styling.
.accordion-panel
: Kontainer untuk konten yang bisa dibuka/tutup.id="panel1"
: ID unik yang dirujuk oleharia-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 mengabaikanaria-labelledby
ini jikaaria-controls
sudah cukup jelas, atau jika konten header sudah cukup deskriptif. Untuk tutorial ini, kita akan fokus padaaria-expanded
danaria-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. Transisitransform
akan menganimasikan putaran ikon..accordion-panel
:max-height: 0
danoverflow: 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
danpadding
akan membuat efek buka/tutup yang mulus.
- Kelas
.is-open
pada.accordion-panel
akan digunakan oleh JS untuk mengaturmax-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:
- Seleksi Elemen: Mengambil semua
.accordion-item
. allowMultipleOpen
: Variabel konfigurasi. Jikafalse
, membuka satu panel akan menutup panel lain yang sedang terbuka.- Looping Melalui Item: Untuk setiap item accordion:
- Ambil header dan panelnya.
- Tambahkan
event listener
click
padaheader
.
- Logika Klik Header:
- Cek apakah panel saat ini sedang terbuka (
isOpen
). - Jika
!allowMultipleOpen
dan panel akan dibuka: PanggilcloseAllOtherPanels()
untuk menutup panel lain. - Toggle Panel Saat Ini:
- Jika Terbuka (ingin menutup):
- Hapus kelas
.is-open
dari header. - Set
aria-expanded
kefalse
. - Set
panel.style.maxHeight = null;
(ini akan membuat CSS mengambilmax-height: 0;
dari stylesheet). - Set padding atas/bawah panel ke
0px
. - Tambahkan atribut
hidden
setelah transisi selesai agar panel tidak bisa di-tab.
- Hapus kelas
- Jika Tertutup (ingin membuka):
- Hapus atribut
hidden
dari panel sebelum mengukurscrollHeight
. - Tambahkan kelas
.is-open
ke header. - Set
aria-expanded
ketrue
. - Menggunakan
requestAnimationFrame
untuk memastikan browser punya waktu memproses penghapusanhidden
sebelum kita mengukurscrollHeight
dan menerapkan style. - Set
panel.style.paddingTop
danpanel.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 CSSmax-height
bekerja dengan benar.
- Hapus atribut
- Jika Terbuka (ingin menutup):
- Cek apakah panel saat ini sedang terbuka (
closeAllOtherPanels(currentItem)
: Fungsi untuk menutup semua panel kecuali panel yang sedang di-klik (jikaallowMultipleOpen
adalahfalse
).- Inisialisasi State Awal: Saat halaman dimuat, kita periksa atribut
aria-expanded
pada setiap header. Jika ada yangtrue
(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. Jikatrue
, 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 denganEnter
atauSpace
. 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.
- Pastikan header (yang merupakan
- 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 mengaturaria-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.