Аккордеон и faq

Создать аккордеон, faq, спойлер и подобное, можно при помощи Div и JavaScript.
Но лучше: Details и Summary

Поддерживается всеми современными браузерами и это семантически правильно оформленный код, при использовании которого будут плюсы:

  1. Людям с ограниченными возможностями проще будет пользоваться вашим сайтом! Их софт (скринридеры и подобное) прекрасно понимает html5 теги и будет правильно обрабатывать их и правильно информировать людей о содержимом.
  2. Улучшится связанность текста, и поисковики смогут более качественно индексировать сайт, так как будут лучше понимать, как связаны между собой видимый и скрытый текст.
  3. Будет доступно управление элементами с клавиатуры и других устройств.
  4. Уменьшается количество javascript кода, который нужно подгружать, что увеличивает скорость загрузки страницы, скорость обработки и корректность.
  5. Улучшаются показатели в Lighthouse, Google PageSpeed и других подобных инструментах.
  6. Работает при выключенном javascript.

Минус:

  1. Старые браузеры не знают таких тегов и не будут скрывать информацию.

HTML:

<details>
    <summary>Покажи-скрой меня</summary>
    <p>Скандинавская мифология — мифология древних скандинавов</p>
</details>
<details open>
    <summary>Покажи-скрой меня 2</summary>
    <p>Основным источником сведений о ней являются тексты поэтической </p>
</details>
<details>
    <summary>Покажи-скрой меня 3</summary>
    <p>Скандинавская мифология — мифология древних скандинавов</p>
</details>

Простой пример Details/Summary

Демонстрация:


С одной стороны выглядит не очень красиво, с другой стороны нейтрально и легко может вписаться во многие дизайны. Кстати, дефолтный вид тега Details очень похож на спойлер от хабра, только нужно чуть перекрасить, сделать подчеркивание и получим семантически правильный, без javascript и дивов, хабровский спойлер.

Спойлер от Хабра

К сожалению, у дефолтного маркера есть два недостатка:

  1. Старые браузеры его не видят.
  2. Вебкит баузеры не позволяют менять символ маркера.

По этой причине, дефолтный маркер надо спрятать и создать свой.


Рассмотрим первый пример Details/Summary с измененным текстовым маркером:

Details/Summary с измененным текстовым маркером

CSS:

summary::-webkit-details-marker{display:none;}
summary::-moz-list-bullet{list-style-type:none;}
summary::marker{display:none;} 
summary {
   display:inline-block;
   padding: .3em .5em .3em .4em;
   font-size:1.4em;
   cursor: pointer;
}
summary:before {  
  content: "+";
  margin-right: .3em;
}
details[open] > summary:before {
  content: "–";
}
summary ~ * {
   padding:0 1em 0 1em;
}
summary:focus {
  outline:0;
  box-shadow: inset 0 0 1px rgba(0,0,0,0.3), inset 0 0 2px rgba(0,0,0,0.3);
}
details{
  display:block;
  margin-bottom: .5rem;
}

Дефолтным маркерам делаем display:none и показываем альтернативный при помощи summary:before {content: "+";}

summary:focus — обводка при помощи box-shadow, это нужно для клавиатуры, чтоб видно было активный элемент и можно было перемещаться клавишей таб и открывать и закрывать при помощи пробела.

Для тега summary я поставил display:inline-block — это чтоб он не растягивался на всю ширину и были кликабельными только слова, а не вся строка.

Демонстрация:


Текстовый маркер справа + простейшая анимация текста и маркера:

Details/Summary - текстовый маркер справа + простейшая анимация текста и маркера

CSS:

summary::-webkit-details-marker{display:none;}
summary::-moz-list-bullet{list-style-type:none;}
summary::marker{display:none;} 
summary {
   display:inline-block;
   padding: .3em .5em .3em .4em;
   font-size:1.4em;
   cursor: pointer;
}
summary:after {  
  content: "+";
  margin-left: .3em;
  display: inline-block;
  transition: transform .5s;
}
details[open] > summary:after {
  transform: scale(1,-1);
}
summary ~ * {
   padding:0 1em 0 1em;
}
summary:focus {
  outline:0;
  box-shadow: inset 0 0 1px rgba(0,0,0,0.3), inset 0 0 2px rgba(0,0,0,0.3);
}
details[open] summary ~ *{ 
  animation: sweep .5s ease-in-out;
}
@keyframes sweep {
  0%    {opacity: 0;}
  100%  {opacity: 1;}
}
details{
  display:block;
  margin-bottom: .5rem;
}

В новом примере я использую для маркера summary:after вместо summary:before, для того чтоб он отображался справа.

Анимация маркера при помощи transform: scale(1,-1);

Всем элементам, которые находится после summary, ставлю анимацию плавного появления при помощи animation: sweep .5s ease-in-out;

Демонстрация:


Svg маркер + анимация поворота:

Details/Summary - svg маркер + анимация поворота

CSS:

summary::-webkit-details-marker{display:none;}
summary::-moz-list-bullet{list-style-type:none;}
summary::marker{display:none;} 
summary {
   display:inline-block;
   padding: .3em .6em .3em 1.5em;
   font-size:1.4em;
   cursor: pointer;
   position: relative;
}
summary:before {  
  left: .3em;
  top: .4em;
  color: transparent;
  background: url("data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjM0IiB2aWV3Qm94PSIwIDAgMjQgMjQiIHdpZHRoPSIzNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNOC41OSAxNi4zNGw0LjU4LTQuNTktNC41OC00LjU5TDEwIDUuNzVsNiA2LTYgNnoiLz48L3N2Zz4=") no-repeat 50% 50% / 1em 1em;
  width: 1em;
  height: 1em;  
  content: "";
  position: absolute;
  transition: transform .5s;
}
details[open] > summary:before {
  transform: rotateZ(90deg);
}
summary ~ * {
   padding:0 1em 0 1em;
}
details[open] summary ~ *{ 
  animation: sweep .5s ease-in-out;
}
@keyframes sweep {
  0%    {opacity: 0;}
  100%  {opacity: 1;}
}
summary:focus {
  outline:0;
  box-shadow: inset 0 0 1px rgba(0,0,0,0.3), inset 0 0 2px rgba(0,0,0,0.3);
}
details{
  display:block;
  margin-bottom: .5rem;
}

Summary:before пришлось серьезно переделать:

  1. Поставить position: absolute; left: .3em; top: .4em; width: 1em; height: 1em;
  2. Текстовому маркеру надо обязательно поставить color: transparent; иначе он будет виден.
  3. Картинку вешаем при помощи background.

Так же нужно у summary поставить отступ padding-left: 1.5em, чтоб текст и иконка не накладывались друг на друга.

Ну и добавляем transform: rotateZ(90deg) для красивого поворота стрелки.

Демонстрация:


Если нам нужна svg иконка справа, то нужно поменять summary:before и вместо left поставить right.

Для summary поставить padding-right: 1.5em;

Details/Summary - svg маркер справа + анимация поворота

CSS:

summary::-webkit-details-marker{display:none;}
summary::-moz-list-bullet{list-style-type:none;}
summary::marker{display:none;} 
summary {
   display:inline-block;
   padding: .3em 1.5em .3em .6em;
   font-size:1.4em;
   cursor: pointer;
   position: relative;
}
summary:before {
  right: .3em;
  top: .4em;
  color: transparent;
  background: url("data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjM0IiB2aWV3Qm94PSIwIDAgMjQgMjQiIHdpZHRoPSIzNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNOC41OSAxNi4zNGw0LjU4LTQuNTktNC41OC00LjU5TDEwIDUuNzVsNiA2LTYgNnoiLz48L3N2Zz4=") no-repeat 50% 50% / 1em 1em;
  width: 1em;
  height: 1em;  
  content: "";
  position: absolute;
  transition: transform .5s;
}
details[open] > summary:before {
  transform: rotateZ(90deg);
}
summary ~ * {
   padding:0 1em 0 1em;
}
details[open] summary ~ *{ 
  animation: sweep .5s ease-in-out;
}
@keyframes sweep {
  0%    {opacity: 0;}
  100%  {opacity: 1;}
}
summary:focus {
  outline:0;
  box-shadow: inset 0 0 1px rgba(0,0,0,0.3), inset 0 0 2px rgba(0,0,0,0.3);
}
details{
  display:block;
  margin-bottom: .5rem;
}

Демонстрация:


Давайте теперь сделаем один из наиболее распространенных примеров создания аккордиона, где будет иконка слева, фон, тени, эффекты:

Аккордион - иконка слева, фон, тени, эффекты

CSS:

body{background: #edf2f7;}
details{
  display:block;
  background: #fff;
  width:400px;
  box-shadow: 0 10px 15px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
  border-radius: 8px;
  overflow:hidden;
  margin-bottom: 1.5rem;
}
summary::-webkit-details-marker{display:none;}
summary::-moz-list-bullet{list-style-type:none;}
summary::marker{display:none;} 
summary {
   display:block;
   padding: .3em .3em .3em 1.4em;
   font-size:1.4em;
   cursor: pointer;
   position: relative;
   border-bottom: 1px solid #e2e8f0;
}
summary:before {  
  top: .4em;
  left: .3em;
  color: transparent;
  background: url("data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjM0IiB2aWV3Qm94PSIwIDAgMjQgMjQiIHdpZHRoPSIzNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNOC41OSAxNi4zNGw0LjU4LTQuNTktNC41OC00LjU5TDEwIDUuNzVsNiA2LTYgNnoiLz48L3N2Zz4=") no-repeat 50% 50% / 1em 1em;
  width: 1em;
  height: 1em;  
  content: "";
  position: absolute;
  transition: transform .5s;
}
details[open] > summary:before {
  transform: rotateZ(90deg);
}
summary ~ * {
   padding: 0 2em 10px 2em;
}
details[open] summary ~ *{ 
  animation: sweep .5s ease-in-out;
}
@keyframes sweep {
  0%    {opacity: 0;}
  100%  {opacity: 1;}
}
summary:focus {
  outline:0;
  box-shadow: inset 0 0 1px rgba(0,0,0,0.3), inset 0 0 2px rgba(0,0,0,0.3);
}

Демонстрация:


Svg маркер справа + эффект зеркального поворота стрелки:

Аккордион - иконка справа + эффект зеркального поворота стрелки

CSS:

body{background: #edf2f7;}
details{
  display:block;
  background: #fff;
  width:400px;
  box-shadow: 0 10px 15px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
  border-radius: 8px;
  overflow:hidden;
  margin-bottom: 1.5rem;
}
summary::-webkit-details-marker{display:none;}
summary::-moz-list-bullet{list-style-type:none;}
summary::marker{display:none;} 
summary {
   display:block;
   padding: .3em 1em .3em .9em;
   border-bottom: 1px solid #e2e8f0;
   font-size:1.4em;
   cursor: pointer;
   position: relative;
}
summary:before {  
  top: .4em;
  right: .3em;
  color: transparent;
  background: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNMTYuNTkgOC41OUwxMiAxMy4xNyA3LjQxIDguNTkgNiAxMGw2IDYgNi02eiIvPjxwYXRoIGQ9Ik0wIDBoMjR2MjRIMHoiIGZpbGw9Im5vbmUiLz48L3N2Zz4=") no-repeat 50% 50% / 1em 1em;
  width: 1em;
  height: 1em;  
  content: "";
  position: absolute;
  transition: transform .5s;
}
details[open] > summary:before {
  transform: scale(1,-1);
}
summary ~ * {
   padding: 0 1em 10px 1.4em;
}
details[open] summary ~ *{ 
  animation: sweep .5s ease-in-out;
}
@keyframes sweep {
  0%    {opacity: 0;}
  100%  {opacity: 1;}
}
summary:focus {
  outline:0;
  box-shadow: inset 0 0 1px rgba(0,0,0,0.3), inset 0 0 2px rgba(0,0,0,0.3);
}

Демонстрация:


Теперь вы можете создавать красивые аккордионы, спойлеры и faq, без JavaScript, на чистом HTML5 и CSS.

Прежде чем убирать outline, 100 раз подумайте, чем вы можете его заменить, чтоб человек мог видеть фокус и мог перемещаться с клавиатуры или других устройств.

Если вам нужно, чтоб при открытии одного спойлера, закрывались остальные, то придется применить javascript, ниже привожу пример простого JS кода, который решит эту проблему.

JavaScript:

var details = document.querySelectorAll("details");
for(i=0;i<details.length;i++) {
  details[i].addEventListener("toggle", accordion);
}
function accordion(event) {
  if (!event.target.open) return;
    var details = event.target.parentNode.children;
    for(i=0;i<details.length;i++) {
      if (details[i].tagName != "DETAILS" || 
         !details[i].hasAttribute('open') || 
         event.target == details[i]) {
         continue;
      }
      details[i].removeAttribute("open");
    }
}

Демонстрация:


Таблица поддержки Details/Summary браузерами.

С уважением, создатель конструктора лэндингов для фрилансеров CMS cPortfolio

Комментарии (13)


  1. keydon2
    02.09.2019 08:30

    Круто. В идеале хочу совсем уйти от js на простеньких сайтах.


  1. ivan386
    02.09.2019 10:28

    Для старых браузеров можно собрать аналог details на css используя checkbox, label, div и селектор :checked.


    Аналогично если не хочется Javascript использовать для автозакрывающихся details можно собрать аналог на radio, label, div и том же селекторе :checked.


    1. iefedorov Автор
      02.09.2019 10:36

      Да, вы правы, ваш вариант — это что-то среднее между details и между кодом на JavaScript! И в ряде случаев :checked это идеальный вариант!)
      Но семантика при этом опускается…


  1. s_berez
    02.09.2019 11:32

    Супер, спасибо за статью!


  1. Tetragonchik
    02.09.2019 11:48

    А я и не знал про тэги такие, спасибо :)


  1. b360124
    02.09.2019 12:16

    Спасибо за статью, но некоторые примеры неверны, хотя в Codepen все верно


    details[open] > summary:before {
      transform: rotateZ(90deg);
    }
    

    а должно быть


    details[open] > summary:before {
      transform: scale(1, -1);
    }


    1. iefedorov Автор
      02.09.2019 12:29

      Спасибо! Поправил!)


  1. namikiri
    02.09.2019 13:13

    border-radius: 8px;

    Вот с этого-то момента Вы всё и испоганили.

    В остальном спасибо за статью, открыл для себя новые возможности HTML5.


    1. iefedorov Автор
      02.09.2019 13:21

      И вам спасибо! Но поясните, чем вам не угодил border-radius? или вам не понравился px? надо было в %?;)


      1. namikiri
        02.09.2019 13:23

        Богомерзкие скругления везде. Это выглядит игрушечно и нетехнологично. Да, субъективно, но и комментарии мы пишем от своего лица, так что имеем право.


        1. iefedorov Автор
          02.09.2019 13:26

          понятно) это действительно субъективно, кому-то нравится, а кого-то раздражает…


        1. AxeFizik
          02.09.2019 19:03

          А что вы понимаете под словом «нетехнологично»?


      1. savostin
        02.09.2019 21:55

        Лично мне не нравятся px, особенно там, где этот размер не привязан, скажем, к изображению в px.