CSS Gridで滑らかに開閉する!JavaScriptで作るアコーディオンコンポーネント

Webサイトでよく使われるアコーディオンメニュー。高さが動的に変わる要素のアニメーションは、かつて max-height などで力技で実装されることが多く、アニメーションの速度が不自然になるのが悩みの種でした。

今回は CSS Gridの grid-template-rows を活用し、高さの計算不要でスムーズに開閉するアコーディオンの作り方を解説します。

アコーディオン(完成品)

See the Pen Untitled by keiichisawai (@sawakei) on CodePen.

コード

HTML

<div class="accordion">
  <div class="accordion__item">
    <button type="button" class="accordion__header">
      アコーディオン1
      <span class="accordion__icon"></span>
    </button>
    <div class="accordion__content">
      <div class="accordion__body">
        ここにはアコーディオン1のコンテンツが入ります。テキストや画像などを配置できます。
      </div>
    </div>
  </div>

  <div class="accordion__item">
    <button type="button" class="accordion__header">
      アコーディオン2
      <span class="accordion__icon"></span>
    </button>
    <div class="accordion__content">
      <div class="accordion__body">
        ここにはアコーディオン2のコンテンツが入ります。テキストや画像などを配置できます。
      </div>
    </div>
  </div>
</div>

CSS

.accordion {
  overflow: hidden;
  border: 1px solid #e0e0e0;
  border-radius: 8px;
}

.accordion__item {
  border-bottom: 1px solid #e0e0e0;
}

.accordion__item:last-child {
  border-bottom: none;
}

.accordion__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  padding: 16px 20px;
  font-weight: bold;
  color: #333;
  cursor: pointer;
  outline: none;
  background: #f9f9f9;
  border: none;
  transition: background-color 0.2s;
}

.accordion__icon {
  width: 10px;
  height: 10px;
  border-right: 2px solid #666;
  border-bottom: 2px solid #666;
  transform: rotate(45deg);
  transition: transform 0.3s;
}


.accordion__item.is-open .accordion__icon {
  transform: rotate(-135deg);
}

.accordion__content {
  display: grid;
  grid-template-rows: 0fr; /*ポイント1*/
  padding: 0 20px;
  background-color: #fff;
  transition: /*ポイント1*/
    grid-template-rows 0.3s,
    padding 0.3s;
}

.accordion__body {
  overflow: hidden; 
}

.accordion__item.is-open .accordion__content {
  grid-template-rows: 1fr; /*ポイント1*/
  padding: 16px 20px;
}

JavaScript

document.addEventListener('DOMContentLoaded', () => {
  const headers = document.querySelectorAll('.accordion__header');

  headers.forEach((header) => {
    header.addEventListener('click', () => {
      const item = header.closest('.accordion__item');
      const allItems = document.querySelectorAll('.accordion__item');

      allItems.forEach((el) => {
        if (el !== item) {
          el.classList.remove('is-open');
        }
      });

      item.classList.toggle('is-open');
    });
  });
});

解説

高さの指定にgrid-template-rowsを使う

ここが一番重要な所だと思います(ポイント1)
下にコンテンツが展開するアニメーションを実装しようと、height:0からheight:autoとしてtransiton:height 0.3s;としてもtransitionは効かないのです。
そこで、grid-template-rowsを代わりに使います

.accordion__content {
  display: grid;
  grid-template-rows: 0fr;
  padding: 0 20px;
  background-color: #fff;
  transition:
    grid-template-rows 0.3s,
    padding 0.3s;
}

.accordion__body {
  overflow: hidden; 
}

.accordion__contentに display:grid; を指定し、通常時はgrid-template-rows:0fr;(行の高さを0にする)にします。
transitionにgrid-template-rowsを指定します。

なお、overflow: hidden;を指定しないと、高さが0の指定でも中身が見えてしまうので、忘れずにしましょう。

.accordion__item.is-open .accordion__content {
  grid-template-rows: 1fr;
}

展開時には、grid-template-rows: 1fr;を指定します。