> For the complete documentation index, see [llms.txt](https://danila-korotkov.gitbook.io/front-end-patterns/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://danila-korotkov.gitbook.io/front-end-patterns/shablony/vkladki-spiski/akkordeon-accordion.md).

# Аккордеон (Accordion)

![](/files/-Malmq_wfXcIxNL32GJM)

## Основные данные&#x20;

Основной ресурс откуда взята информация - <https://ebay.gitbook.io/mindpatterns/disclosure/accordion>

Каждая панель аккордеона отделена интерактивным заголовком панели, которую в любой момент можно открыть.

Часто используется в боковых панелях и меню навигации для постепенного раскрытия ссылок и фильтров.

Хорошая статья с примерами настройки и применения `<details>`: <https://habr.com/ru/post/477520/>

Очень много примеров `<details>` использования с кодом: <https://freefrontend.com/html-details-summary-css/>

## Терминология

* **accordion**: шаблон в целом, состоящий из следующих частей
* **panel**: каждая панель в аккордеоне - это виджет раскрытия деталей
* **header**: заголовок, который отображает интерактивную сводку содержимого панели
* **heading**: заголовок, который находится внутри элемента и отражает суть панели
* **auto-collapse**: свойство аккордеона,  при котором автоматически сворачиваются открытые панели при открытии новой панели

## Лучшая практика

Каждый заголовок (header) находится в естественном индексе табуляции. Этот естественный tabindex нельзя удалять или изменять.

По умолчанию все панели могут быть в открытом, развернутом состоянии.

*При желании* аккордеон может быть ограничен отображением только одной панели содержимого за раз (т.е. открытие панели закроет любую другую открытую панель). Это называется автоматически сворачивающимся аккордеоном.

## Интерактивный дизайн

### Управление клавиатурой

Нажатие клавиши `TAB`должно перемещать фокус клавиатуры с одного заголовка на другой. Он также будет перемещать фокус через любые интерактивные элементы внутри открытых панелей.

Точно так же нажатие клавиш `SHIFT-TAB` перемещает фокус назад по заголовкам и содержимому интерактивной панели.

Нажатие клавиши `ПРОБЕЛ`или `ВВОД`в заголовке с фокусом клавиатуры должно открывать панель. Для автоматического сворачивания аккордеонов любая другая открытая панель должна закрываться.

### Screenreader

Виртуальный курсор **должен** иметь возможность перемещаться от одного заголовка к другому. ‌

С виртуальным курсором на заголовке, он должен иметь возможность открывать панель с помощью имитации события щелчка. Для аккордеонов с автоматическим сворачиванием любая другая открытая панель должна закрываться, но об этом не следует объявлять.

## Код

В шаблоне «аккордеон» активно используется тег "details" HTML. Старые браузеры, включая IE11 и Edge, изначально не поддерживают тег сведений, поэтому требуют полифилла.

{% hint style="danger" %}
При использовании details нет возможности анимировать открытие
{% endhint %}

HTML-элемент `<details>` используется для раскрытия скрытой (дополнительной) информации.

Виджет раскрытия обычно представлен на экране с использованием небольшого треугольника, который поворачивается, чтобы показать состояние открытия / закрытия, с меткой рядом с треугольником. Если первый дочерний элемент элемента `<details>` является `<summary>`, содержимое элемента `<summary>` используется в качестве метки для виджета раскрытия.

{% tabs %}
{% tab title="XML/HTML/SVG" %}

```markup
<ul class="accordion" role="list" aria-roledescription="accordion">
    <li>
        <details class="accordion__details" open>
            <summary><h4>Buying</h4></summary>
            <ul>
                <li><a href="http://www.ebay.com">Purchases</a></li>
                <li><a href="http://www.ebay.com">Bids/Offers</a></li>
                <li><a href="http://www.ebay.com">Didn't Win</a></li>
            </ul>
        </details>
    </li>
    <li>
        <details class="accordion__details">
            <summary><h4>Selling</h4></summary>
            <ul>
                <li><a href="http://www.ebay.com">Sold</a></li>
                <li><a href="http://www.ebay.com">Bids/Offers</a></li>
                <li><a href="http://www.ebay.com">Didn't Swell</a></li>
            </ul>
        </details>
    </li>
</ul>
```

{% endtab %}
{% endtabs %}

Обратите внимание, что в некоторых браузерах роль неявного списка удаляется, когда применяется CSS list-style-type: none, поэтому мы применили role = list в нашей разметке, чтобы гарантировать явную роль.

### CSS

Для смены стандартного треугольника раскрытия:

```css
summary { list-style-image: url(right-arrow.svg); }
summary::-webkit-details-marker { background: url(right-arrow.svg); color: transparent; }
```

Анимация раскрытия:

```css
details[open] summary ~ * {
  animation: sweep .5s ease-in-out;
}

@keyframes sweep {
  0%    {opacity: 0; margin-left: -10px}
  100%  {opacity: 1; margin-left: 0px}
}
```

### JavaScript

Тэг `details` требует [polyfill](https://github.com/javan/details-element-polyfill) для браузеров которые не поддерживают его нативно.

Код для загрузки полифила только для ие:

```javascript
<script type="text/javascript">
    if(/MSIE \d|Trident.*rv:/.test(navigator.userAgent))
        document.write('<script src="somescript.js"><\/script>');
</script>
```

Основной код указан выше и служит для того, что бы закрывать открытую вкладку при выборе новой.

## Код (с возможность анимации)

```html
<ul class="services__list" role="list" aria-roledescription="accordion">
   <li class="services__details">
      <button class="btn services__summary" aria-expanded="false">
          <h3 class="services__header">Заголовок</h3>
          <span class="services__icon"></span>
      </button>
      <div class="services__content" aria-hidden="true">
         <p class="services__text">Lorem ipsum dolor sit, amet consectetur adipisicing elit. Praesentium, quod.</p>
      </div>
   </li>  
   <li class="services__details">
      <button class="btn services__summary" aria-expanded="false">
          <h3 class="services__header">Заголовок</h3>
          <span class="services__icon"></span>
      </button>
      <div class="services__content" aria-hidden="true">
         <p class="services__text">Lorem ipsum dolor sit, amet consectetur adipisicing elit. Praesentium, quod.</p>
      </div>
   </li>         
 </ul>
```

```scss
 &__content {
         opacity: 0;
         max-height: 0;
         will-change: max-height;
         overflow: hidden;
         transition: opacity, max-height  0.3s ease-out;
         box-sizing: content-box;
      }

      &__content[aria-hidden="false"]{
         opacity: 1;
         padding: 10px 10px;
      }
```

```javascript
/**
 * Аккордеон с анимацией (через button)
 * el - класс аккордеона
 * closeAll - определят, требуется ли закрывать остальные вкладки при открытии
 */
class AccordionBtn {
   constructor(el, closeAll = false) {
      this.accrdion = document.querySelector(el);
      this.closeAll = closeAll;
      if (!this.accrdion) {
         console.error(`Не найден аккордеон - ${el}!`);
         return;
      }
      this.init();
   }

   init() {
      this.accItmesBtns = this.accrdion.querySelectorAll("button[aria-expanded]");
      if (!this.accItmesBtns) console.error("Не найдены кнопки содержащие аттрибут aria-expanded");

      this.accItmesBtns.forEach((itemBtn) => {
         itemBtn.addEventListener("click", (e) => {
            const btn = e.currentTarget;
            const openBtn = btn.getAttribute("aria-expanded") === "true" ? true : false;

            if (this.closeAll) {
               this.showOne(btn);
            }
            const context = btn.nextElementSibling;
            if (!context || !context.hasAttribute("aria-hidden")) console.error("Не найден блок с контентом содержащий аттрибут aria-hidden");
            const openContext = context.getAttribute("aria-hidden") === "true" ? true : false;
            const heightContext = context.style.maxHeight === "" || context.style.maxHeight === "0px" ? context.scrollHeight : 0;

            btn.setAttribute("aria-expanded", !openBtn);
            context.setAttribute("aria-hidden", !openContext);
            context.style.maxHeight = heightContext + "px";
         });
      });
   }

   showOne(btn) {
      this.accItmesBtns.forEach((itemBtn) => {
         if (itemBtn !== btn) {
            itemBtn.setAttribute("aria-expanded", "false");
            if (itemBtn.nextElementSibling.hasAttribute("aria-hidden")) {
               itemBtn.nextElementSibling.setAttribute("aria-hidden", "true");
               itemBtn.nextElementSibling.style.maxHeight = "0px";
            }
         }
      });
   }
}
```

## ARIA

* **aria-roledescription**: определяет удобочитаемое, локализованное для автора описание роли элемента. В данном случае «аккордеон».
