Создание элемента спойлер: нативный HTML-тег <details>

📂 Категория: HTML
🏷️ Теги:

Спойлер (или аккордеон) — это один из самых популярных компонентов на любом сайте. Чаще всего его используют в секциях с частыми вопросами (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;
}

Похожие статьи