[CSS] `grid-template-rows`に`transition`が効くのでJS使わずにアコーディオン作ってみた件

4 min read

背景

JavaScript を利用せずに HTML と CSS だけでアコーディオン UI を実装する場合、折りたたみ部分の開閉アニメーションをどうするのかが肝になる。

最近では JavaScript を利用しないでアコーディオン UI を実現するために <details><summary> を利用するケースもある。ただ、この場合は折りたたみ部分の開閉アニメーションは利用できない。

よくある実装方法

HTML と CSS だけで実装する場合、以下のように開閉時にmax-heightを変えてtransitionさせることでアニメーションを実現する方法がある。

.collapse_body {
  max-height: 0;
  overflow: hidden;
  transition: max-height 0.3s ease-in-out;
}

.collapse_control:checked ~ .collapse_body {
  max-height: 100vh;
}

b.0218.jpCSSのみでアコーディオンメニューを実装する方法背景Q&#x26;A 表のように「質問部分をクリックすると答えを表示する」ような動作を JavaScript は使用せず実装する。デモSee the Pen &#x3C;a href='https://codepen.io/hiro0218/pen/JaYqzM/'>accord

実装方法

max-heightではなく、gridgrid-template-rowsをアニメーションさせる。

説明用で簡素にしているが、実装は以下の通り。

<div class="Accordion">
  <label class="Accordion-header">
    <input type="checkbox" hidden />見出し
  </label>
  <div class="Accordion-content">
    <div class="Accordion-content__inner">中身</div>
  </div>
</div>
/* 必要なものだけ抜粋 */
.Accordion {
  &:has(.Accordion-header input:checked) {
    .Accordion-content {
      grid-template-rows: 1fr;
    }
  }
}

.Accordion-content {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 0.3s ease-in-out;
}

.Accordion-content__inner {
  overflow: hidden;
}

大まかに要素の高さを変えてアニメーションさせる手順は下記の通り。

  • .Accordion-content:
    • 折りたたみ対象の要素にdisplay: gridgrid-template-rows0frで指定する
      • transitiongrid-template-rowsに対して指定する
    • 開閉状態時、grid-template-rows1frにする
  • .Accordion-content__inner:
    • 折りたたみ対象の要素の中身がはみ出してしまうため、overflow: hidden;を指定しておく

:has()が使えない場合

JavaScript を利用しないため、開閉状態のフラグをinput[type=checkbox]にもたせている。それを CSS で判定する必要があるが、それは:has(.Accordion-header input:checked)というセレクタで実現している。
:has()を利用できない環境の場合は、隣接セレクタや子孫セレクタなどを利用する必要がある。

<div class="Accordion">
  <label class="Accordion-header" for="AccordionCheck"> 見出し </label>
  <input type="checkbox" id="AccordionCheck" class="Accordion-check" hidden />
  <div class="Accordion-content">
    <div class="Accordion-content__inner">中身</div>
  </div>
</div>
.Accordion-check:checked ~ .Accordion-content {
  grid-template-rows: 1fr;
}

デモ