Спойлер (или аккордеон) — это один из самых популярных компонентов на любом сайте. Чаще всего его используют в секциях с частыми вопросами (FAQ), чтобы скрыть длинные ответы и не перегружать страницу.
Раньше создание такого блока требовало написания JavaScript-кода или подключения целых библиотек. Нужно было вешать обработчики кликов, вручную управлять классами и следить за доступностью. Но прогресс не стоит на месте, и теперь у нас есть нативное и семантичное решение — элемент <details>.
При этом, использовать какие-то нестандартные способы нет никакого смысла. У стандартного подхода есть неоспоримые преимущества:
- Работает везде. Это полностью рабочее решение, даже если в браузере отключен JavaScript.
- Группировка (аккордеон). Если нужно, чтобы при открытии одного спойлера, другой автоматически закрывался, просто добавьте одинаковый атрибут
nameк группе элементов<details>, как у radio-кнопок. - Семантика. Поисковые системы и вспомогательные технологии (скринридеры) понимают, что это за элемент, без дополнительных подсказок.
- Состояния. Атрибут
openпозволяет держать элемент открытым по умолчанию.
Вот как выглядит базовая структура:
<details name="faq-1" open>
<summary>
Заголовок вопроса
<svg aria-hidden="true"><use href="#chevron-up"></use></svg>
</summary>
<p>Здесь находится скрытый текст ответа. Можно добавлять списки или изображения.</p>
<p>Абзац текста.</p>
<p>И ещё один абзац.</p>
</details>
Стилизация и плавное раскрытие
Всё, что нам остаётся сделать – стилизовать наш компонент. Для начала нужно удалить из него стандартную стрелку-маркер.
summary {
list-style: none;
appearance: none;
&::marker {
display: none;
content: '';
}
&::-webkit-details-marker {
display: none;
}
}
А это – весь секрет плавного раскрытия контента с помощью псевдо-элемента ::details-content.
details {
--_transition-duration: 0.5s;
overflow: hidden;
&::details-content {
block-size: 0;
transition:
block-size var(--_transition-duration),
content-visibility var(--_transition-duration) allow-discrete;
}
&[open]::details-content {
block-size: auto;
}
}
Как это работает: свойство block-size (аналог height) плавно меняется от 0 до автоматической высоты контента (auto). Параметр allow-discrete разрешает анимацию дискретных свойств, а overflow: hidden обрезает текст, пока блок не раскрылся.
На момент 2026 года свойство ::details-content хорошо поддерживается в современных браузерах, но
в Firefox, Safari и старых версиях браузера анимация может не работать. Для обратной совместимости
можно использовать трюк с анимацией max-height.
Нам остаётся только подобрать свою иконку плюса или шеврона, а так же стилизовать компонент с помощью CSS.
Доступность (Accessibility)
Поскольку мы полностью переопределяем внешний вид, важно не сломать пользовательский опыт для людей, использующих скринридеры:
Клавиатура: Элемент <summary> по умолчанию доступен для фокуса с клавиатуры (Tab). Убедитесь, что стили :focus-visible не стерты.
Иконка: Если вы используете SVG внутри <summary>, добавьте к нему атрибут aria-hidden="true". Это декоративный элемент, скринридер не должен на нем останавливаться.
Примеры рабочих компонентов
Вариант 1 - Open
Здесь находится скрытый текст ответа. Можно добавлять списки или изображения.
Абзац текста.
И ещё один абзац.
Вариант 1 - Close
Здесь находится скрытый текст ответа. Можно добавлять списки или изображения.
Абзац текста.
И ещё один абзац.
Вариант 2 - Open
Здесь находится скрытый текст ответа. Можно добавлять списки или изображения.
Абзац текста.
И ещё один абзац.
Вариант 2 - Close
Здесь находится скрытый текст ответа. Можно добавлять списки или изображения.
Абзац текста.
И ещё один абзац.
Вариант 3 - Open
Здесь находится скрытый текст ответа. Можно добавлять списки или изображения.
Абзац текста.
И ещё один абзац.
Вариант 3 - Close
Здесь находится скрытый текст ответа. Можно добавлять списки или изображения.
Абзац текста.
И ещё один абзац.
Вариант 4 - Open
Здесь находится скрытый текст ответа. Можно добавлять списки или изображения.
Абзац текста.
И ещё один абзац.
Вариант 4 - Close
Здесь находится скрытый текст ответа. Можно добавлять списки или изображения.
Абзац текста.
И ещё один абзац.
Скопировать готовый код
HTML для элемента общий для всех вариантов, там меняется только класс в корневом div, например, variant-1.
<div class="variant-1">
<details name="faq-1" open>
<summary>
Вопрос 1
<svg aria-hidden="true">
<use href="#chevron-up"></use>
</svg>
</summary>
<p>Здесь находится скрытый текст ответа. Можно добавлять списки или изображения.</p>
<p>Абзац текста.</p>
<p>И ещё один абзац.</p>
</details>
</div>
Это спрайт для иконки (из библиотеки Tabler). Его нужно добавить в body страницы (или переписать ссылки в use на отдельный файл), чтобы SVG подключались.
<svg xmlns="http://www.w3.org/2000/svg" style="display: none">
<symbol
id="chevron-up"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M6 9l6 6l6 -6"></path>
</symbol>
</svg>
Вариант 1
Здесь находится скрытый текст ответа. Можно добавлять списки или изображения.
Абзац текста.
И ещё один абзац.
Показать CSS код
.variant-1 {
--_transition-duration: 0.5s;
--primary-color: oklch(54.6% 0.245 262.881);
& > details + details {
margin-top: 2rem;
}
}
.variant-1 details {
border-bottom: 1px solid oklch(86.9% 0.022 252.894);
transition: border-color 0.3s;
overflow: hidden;
&::details-content {
block-size: 0;
transition:
block-size var(--_transition-duration),
content-visibility var(--_transition-duration) allow-discrete;
}
& > :not(summary) {
color: oklch(44.6% 0.043 257.281);
}
&[open] {
border-color: var(--primary-color);
}
&[open]::details-content {
block-size: auto;
}
&[open] summary {
color: var(--primary-color);
}
@media (hover: hover) {
&:has(summary:hover) {
border-color: var(--primary-color);
}
}
}
.variant-1 summary {
display: flex;
justify-content: space-between;
align-items: center;
gap: 2rem;
padding-bottom: 1rem;
color: black;
font-size: 1.25rem;
font-weight: 700;
list-style: none;
appearance: none;
cursor: pointer;
user-select: none;
@media (hover: hover) {
&:hover {
color: var(--primary-color);
}
}
&::marker {
display: none;
content: '';
}
&::-webkit-details-marker {
display: none;
}
}
.variant-1 details > :last-child {
padding-bottom: 1rem;
}
.variant-1 summary > svg {
flex-shrink: 0;
width: 2rem;
height: 2rem;
color: oklch(70.4% 0.04 256.788);
transition-duration: var(--_transition-duration);
}
.variant-1 details[open] > summary svg {
rotate: 180deg;
} Вариант 2
Здесь находится скрытый текст ответа. Можно добавлять списки или изображения.
Абзац текста.
И ещё один абзац.
Показать CSS код
.variant-2 {
--_transition-duration: 0.5s;
--primary-color: black;
& > details + details {
margin-top: 2rem;
}
}
.variant-2 details {
overflow: hidden;
border: 2px solid var(--primary-color);
&::details-content {
block-size: 0;
background-color: white;
transition:
block-size var(--_transition-duration),
content-visibility var(--_transition-duration) allow-discrete;
}
& > :not(summary) {
padding-inline: 1.5rem;
color: oklch(44.6% 0.043 257.281);
}
&[open]::details-content {
block-size: auto;
}
&[open] summary {
background-color: var(--primary-color);
color: white;
}
}
.variant-2 summary {
display: flex;
justify-content: space-between;
align-items: center;
gap: 2rem;
padding: 1rem 1.5rem;
background-color: oklch(96.8% 0.007 247.896);
color: var(--primary-color);
font-size: 1.25rem;
font-weight: 700;
transition: background-color 0.3s;
list-style: none;
appearance: none;
cursor: pointer;
user-select: none;
@media (hover: hover) {
&:hover {
background-color: var(--primary-color);
color: white;
}
}
&::marker {
display: none;
content: '';
}
&::-webkit-details-marker {
display: none;
}
}
.variant-2 summary + * {
padding-top: 1rem;
}
.variant-2 details > :last-child {
padding-bottom: 1rem;
}
.variant-2 summary > svg {
flex-shrink: 0;
width: 2rem;
height: 2rem;
transition-duration: var(--_transition-duration);
}
.variant-2 details[open] > summary svg {
rotate: 180deg;
} Вариант 3
Здесь находится скрытый текст ответа. Можно добавлять списки или изображения.
Абзац текста.
И ещё один абзац.
Показать CSS код
.variant-3 {
--_transition-duration: 0.5s;
--_border-radius: 0.5rem;
--primary-color: oklch(54.6% 0.245 262.881);
--primary-active-color: oklch(42.4% 0.199 265.638);
& > details + details {
margin-top: 2rem;
}
}
.variant-3 details {
clip-path: inset(0 round var(--_border-radius));
&::details-content {
block-size: 0;
margin-top: -1px;
background-color: white;
border-bottom-left-radius: var(--_border-radius);
border-bottom-right-radius: var(--_border-radius);
border-inline: 1px solid var(--primary-color);
border-bottom: 1px solid var(--primary-color);
transition:
block-size var(--_transition-duration),
content-visibility var(--_transition-duration) allow-discrete;
}
& > :not(summary) {
padding-inline: 1.5rem;
color: oklch(44.6% 0.043 257.281);
}
&[open]::details-content {
block-size: auto;
}
&[open] summary {
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
@media (hover: hover) {
&:has(summary:hover)::details-content {
border-color: var(--primary-active-color);
}
}
}
.variant-3 summary {
display: flex;
justify-content: space-between;
align-items: center;
gap: 2rem;
padding: 1rem 1.5rem;
background-color: var(--primary-color);
color: white;
font-size: 1.25rem;
font-weight: 700;
border-radius: var(--_border-radius);
transition-property: background-color, border-radius;
transition-duration: 0.3s, var(--_transition-duration);
list-style: none;
appearance: none;
cursor: pointer;
user-select: none;
@media (hover: hover) {
&:hover {
background-color: var(--primary-active-color);
}
}
&::marker {
display: none;
content: '';
}
&::-webkit-details-marker {
display: none;
}
}
.variant-3 summary + * {
padding-top: 1rem;
}
.variant-3 details > :last-child {
padding-bottom: 1rem;
}
.variant-3 summary > svg {
flex-shrink: 0;
width: 2rem;
height: 2rem;
transition-duration: var(--_transition-duration);
}
.variant-3 details[open] > summary svg {
rotate: 180deg;
} Для варианта №4 нужно немного изменить разметку – иконку обернули во wrapper для стилизации круга.
<div class="variant-4">
<details name="faq-4">
<summary>
Вариант 4
<span class="icon-wrapper" aria-hidden="true">
<svg>
<use href="#chevron-up"></use>
</svg>
</span>
</summary>
<p>Здесь находится скрытый текст ответа. Можно добавлять списки или изображения.</p>
<p>Абзац текста.</p>
<p>И ещё один абзац.</p>
</details>
</div>
Вариант 4
Здесь находится скрытый текст ответа. Можно добавлять списки или изображения.
Абзац текста.
И ещё один абзац.
Показать CSS код
.variant-4 {
--_transition-duration: 0.5s;
--_border-radius: 1rem;
--primary-color: oklch(54.6% 0.245 262.881);
--primary-low-color: oklch(93.2% 0.032 255.585);
& > details + details {
margin-top: 2rem;
}
}
.variant-4 details {
overflow: hidden;
border-radius: var(--_border-radius);
&::details-content {
block-size: 0;
background-color: var(--primary-low-color);
transition:
block-size var(--_transition-duration),
content-visibility var(--_transition-duration) allow-discrete;
}
& > :not(summary) {
padding-inline: 1.5rem;
color: oklch(44.6% 0.043 257.281);
}
&[open]::details-content {
block-size: auto;
}
&[open] summary {
background-color: var(--primary-color);
color: white;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
}
.variant-4 summary {
display: flex;
justify-content: space-between;
align-items: center;
gap: 2rem;
padding: 1rem 1.5rem;
background-color: var(--primary-low-color);
color: black;
font-size: 1.25rem;
font-weight: 700;
border-radius: var(--_border-radius);
transition-property: background-color, color, border-radius;
transition-duration: 0.3s, 0.3s, var(--_transition-duration);
list-style: none;
appearance: none;
cursor: pointer;
user-select: none;
@media (hover: hover) {
&:hover {
background-color: var(--primary-color);
color: white;
}
}
&::marker {
display: none;
content: '';
}
&::-webkit-details-marker {
display: none;
}
}
.variant-4 summary + * {
padding-top: 1rem;
}
.variant-4 details > :last-child {
padding-bottom: 1rem;
}
.variant-4 summary > .icon-wrapper svg {
width: 2rem;
height: 2rem;
color: black;
transition-duration: var(--_transition-duration);
}
.variant-4 summary .icon-wrapper {
flex-shrink: 0;
width: 2.5rem;
height: 2.5rem;
display: grid;
place-content: center;
background: white;
border-radius: 100%;
}
.variant-4 details[open] > summary svg {
rotate: 180deg;
}