Аккордеон
Компонент, где по клику на заголовок раскрывается/закрывается блок контента с плавной анимацией высоты.
Задача
Нужен FAQ или секция с разворачиваемыми ответами. Одновременно открыт только один пункт (поведение аккордеона).
Решение
Вариант 1 — нативный <details> (без JS)
<div class="accordion">
<details class="accordion__item">
<summary class="accordion__trigger">Вопрос первый</summary>
<div class="accordion__content">
<p>Ответ на первый вопрос. Текст может быть длинным.</p>
</div>
</details>
<details class="accordion__item">
<summary class="accordion__trigger">Вопрос второй</summary>
<div class="accordion__content">
<p>Ответ на второй вопрос.</p>
</div>
</details>
</div>
.accordion__item {
border: 1px solid #e2e8f0;
border-radius: 8px;
margin-bottom: 8px;
overflow: hidden;
}
.accordion__trigger {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 20px;
cursor: pointer;
font-weight: 600;
list-style: none; /* убрать стандартный маркер */
user-select: none;
}
.accordion__trigger::after {
content: '▾';
transition: transform 0.3s;
}
details[open] .accordion__trigger::after {
transform: rotate(-180deg);
}
.accordion__content { padding: 0 20px 16px; color: #475569; }
Вариант 2 — JS с анимацией высоты через max-height
<div class="accordion" id="faq">
<div class="accordion__item">
<button class="accordion__trigger" aria-expanded="false">
Вопрос 1 <span class="accordion__icon">▾</span>
</button>
<div class="accordion__panel" hidden>
<div class="accordion__content"><p>Ответ на вопрос 1.</p></div>
</div>
</div>
<!-- ещё пункты -->
</div>
const accordion = document.getElementById('faq');
accordion.addEventListener('click', (e) => {
const trigger = e.target.closest('.accordion__trigger');
if (!trigger) return;
const item = trigger.closest('.accordion__item');
const panel = item.querySelector('.accordion__panel');
const isOpen = trigger.getAttribute('aria-expanded') === 'true';
// Закрыть все пункты
accordion.querySelectorAll('.accordion__trigger').forEach((t) => {
t.setAttribute('aria-expanded', 'false');
t.closest('.accordion__item').querySelector('.accordion__panel').hidden = true;
});
// Открыть текущий (если был закрыт)
if (!isOpen) {
trigger.setAttribute('aria-expanded', 'true');
panel.hidden = false;
}
});
Ключевые моменты
<details>+<summary>— нативное решение без JS; ограничение: нет анимации и нет принудительного «один открыт».aria-expandedна кнопке — обязателен для screen reader.- При
hiddenатрибуте элемент скрыт; для анимации используйmax-height+transitionили Web Animations API. - Делегирование клика на родительский
.accordion— один обработчик для любого числа пунктов.
Варианты
- Tabbed interface вместо аккордеона — если пункты можно просматривать все сразу.
- CSS
@keyframes+grid-template-rows: 0fr → 1fr— современная анимация раскрытия без JS.