アコーディオンUIは、限られたスペースに多くの情報を整理して表示できる便利なコンポーネントです。FAQページやサイドメニュー、設定画面など、さまざまな場面で活用されています。
昔はアコーディオンの開閉機能を実装するにはJavaScriptが必須でしたが、HTML5で導入されたdetailsとsummary要素を使えば、JavaScriptなしでアクセシブルなアコーディオンを実装できます。
このページでは、detailsとsummaryを用いたアコーディオンの構築方法を解説します。
details/summary要素についての基礎知識
details/summary要素には開閉機能がネイティブで備わっており、JavaScriptが必要ありません。
また、CSSでスタイリングしなくてもデフォルトで左側に三角形のマーカー(▶)が表示され、展開時には方向が変わる(▼)ようになっており、HTMLだけでも最低限の機能と見た目が担保されています。
まずはシンプルな例を見てみましょう。
<summary>クリックして詳細を表示できます</summary>
summary要素は、details要素の最初の子要素として配置する必要があります。
このHTMLを書くだけで、以下のようになります。
Preview
クリックして詳細を表示できます
ここに詳細な内容が入ります。
details/summary を使うメリット
- 開閉機能がネイティブで備わっている
- Tabキーでフォーカス移動、Enter/Spaceキーで開閉が可能
- スクリーンリーダー対応で、「折りたたみ」「展開済み」などの状態が自動的に通知される
- ページ内テキスト検索時に、コンテンツが隠れていても自動で展開してくれる
- name属性を使えば展開する要素を一つに制限してくれる
独自にdiv要素やbutton要素でアコーディオンを実装する場合、これらのアクセシビリティ対応を自前で実装する必要がありJavaScriptも必要になってきますが、details/summaryを使えばその手間が省けます。
summary内に見出しタグを配置した場合の問題点
summary内に見出しタグ(h1~h6)を配置した場合、スクリーンリーダーが見出しとして検出できなくなってしまうという問題があります。
これはsummaryが暗黙的にrole="button"扱いになっていることが原因のようで、アクセシビリティツリー上ではその中に配置されている見出しのロールを検出できません。
HTMLの仕様としては summary > hタグ の構造は許可されているものの、スクリーンリーダーでは正しく反映されない状況となっています。
VoiceOver読み上げ時にその「三角形の選択ボタン」の読み上げがいちいち入るのが邪魔な気がする(読み上げ対象から除外する方法がわからなかった)
[open]属性
details要素が開いている状態では、HTML上にopen属性が自動的に付与されます。これを属性セレクタで参照することで、開閉状態に応じたスタイルを適用できます。
/* details要素が開いている時のスタイル */
デフォルトの三角形マーカーを非表示にする方法
summary要素の三角形マーカーは、Chrome/Edge/Firefoxなどでは、summary要素のdisplayプロパティをlist-item以外に変更することで自動的に非表示になります。
Safariでは、::-webkit-details-markerという独自の疑似要素でこのマーカーがスタイリングされているため、以下のようにして明示的に非表示にするスタイルを書く必要があります。
summary::-webkit-details-marker {
details/summaryで実装するアコーディオン
それでは、実際にdetailsとsummaryを使ってアコーディオンを実装してみましょう。
<div class="accordions">
<details class="accordion">
<summary class="accordion__title">
<span>Q: 配送にはどのくらいかかりますか?</span>
</summary>
<div class="accordion__content">
<p>ご注文から通常3〜5営業日でお届けします。お急ぎの場合は、お届け先や在庫状況により最短翌日配送も可能です。</p>
</div>
</details>
<details class="accordion">
<summary class="accordion__title">
<span>Q: 返品・交換は可能ですか?</span>
</summary>
<div class="accordion__content">
<p>商品到着後14日以内であれば、未開封・未使用に限り返品・交換を承ります。詳細は返品ポリシーをご確認ください。</p>
</div>
</details>
<details class="accordion">
<summary class="accordion__title">
<span>Q: 支払い方法は何が使えますか?</span>
</summary>
<div class="accordion__content">
<p>クレジットカード、銀行振込、コンビニ決済、各種電子マネー、代金引換がご利用いただけます。</p>
</div>
</details>
</div>
/* アコーディオン全体のコンテナスタイル */
.accordions {
--bdc: #d7dee5;
border: solid 1px var(--bdc);
border-radius: 8px;
overflow: clip;
}
/* 2つ目以降のアコーディオンには上部にボーダーを追加 */
.accordion + .accordion {
border-top: solid 1px var(--bdc);
}
/* コンテンツエリアのパディング */
.accordion__content {
padding: 1.25em;
}
/* タイトル部分のスタイル */
.accordion__title {
display: flex; /* list-item 以外にすることでデフォルトの三角アイコンは非表示になる */
align-items: center;
justify-content: space-between;
gap: 1em;
padding: 1em 1.25em;
cursor: pointer;
background-color: color-mix(in srgb, var(--bdc), transparent 80%);
/* Tab操作によるフォーカス時のアウトラインを少し内側に寄せる( overflow:clip で見えづらくなることへの対処) */
outline-offset: -1px;
/* Safariで表示されるデフォルトの三角形アイコンを削除 */
&::-webkit-details-marker {
display: none;
}
}
/* タイトルホバー時、背景色を少し濃くする */
@media (any-hover: hover) {
.accordion__title:hover {
background-color: color-mix(in srgb, var(--bdc), transparent 60%);
}
}
/* Tabキーでのフォーカス時も同じスタイリング */
.accordion__title:focus-visible {
background-color: color-mix(in srgb, var(--bdc), transparent 60%);
}
/* アイコン擬似要素 */
.accordion__title::after {
content: '';
display: block;
width: 1em;
height: 1em;
background-color: currentColor;
/* clip-path で > アイコンを描画し、回転させる */
clip-path: polygon(
calc(35% + 4%) calc(10% - 4%),
calc(75% + 4% * 2) 50%,
calc(35% + 4%) calc(90% + 4%),
calc(35% - 4%) calc(90% - 4%),
calc(75% - 4% * 2) 50%,
calc(35% - 4%) calc(10% + 4%)
);
rotate: 90deg;
transition: scale 0.3s;
}
/* アコーディオンが開いている時、アイコンを反転 */
.accordion[open] > .accordion__title::after {
scale: -1 1;
}
HTMLの構造はシンプルです。.accordionsというラッパー要素の中に、複数のdetails要素を配置しています。各details要素にはsummaryでタイトルを、その後に続く<div>でコンテンツを定義しています。
コンテンツの開閉アニメーションについて
アコーディオンUIを実装する際、コンテンツ部分の開閉にスムーズなスライドアニメーションを付けるケースがよくあります。
これまで、CSSだけではdetailsの開閉アニメーションを実装することは困難でした。
従来の問題は次の2点。
height: auto ではアニメーションができない。
open属性が外れている時、コンテンツがレンダリングから完全に除外されてしまう。(display:none からフェードインできないのと同じ)
しかしながら、最新のCSSでは、CSSだけでもアニメーションが可能になりつつあります。
- 1を解決するTips :
grid-template-rowsを0fr~1frへトランジションさせるというテクニックがあります。また、将来的にはinterpolate-size: allow-keywords;を使うことでheight:autoのアニメーションが可能になります。
- 2を解決するTips :
::details-content擬似要素を使うことで、コンテンツ用のslot要素に対してスタイルを付与することができるようになり、アニメーションも可能になりました。
interpolate-size, ::details-contentはまだブラウザ対応が完全ではないため積極的に導入はしずらいですが、将来的にはこれらを使ってCSSだけで開閉アニメーションを実装できるようになります。
ブラウザサポート状況(2025年12月時点)
interpolate-size
129 (2024/9)~
::details-content
131 (2024/11)~
143 (2025/9)~
18.4 (2025/3)~
::details-contentは主要ブラウザで広くサポートされはじめていますが、interpolate-sizeはChromium系のみの対応となっています。非対応ブラウザではアニメーションなしで即座に開閉してしまいます。
CSSだけで開閉アニメーションを実装する例
::details-content に対して 0fr~1frのトランジションTispを使い、CSSだけで開閉アニメーションを実装する例を紹介します。
(interpolate-size: allow-keywords;を使ってheightをアニメーションさせた方が動きが滑らかな気がしたので、将来的にはそちらを使いたいですが、ひとまずサポート状況を優先しています。)
<div class="accordions">
<details class="accordion" name="demo">
<summary class="accordion__title">
<span>Q: 配送にはどのくらいかかりますか?</span>
<span class="accordion__icon" aria-hidden="true"></span>
</summary>
<div class="accordion__content">
<p>ご注文から通常3〜5営業日でお届けします。お届け先の地域や在庫状況によって、お届けまでの日数が前後する場合がございます。</p>
<p>お急ぎの場合は、翌日配送オプションもご利用いただけます(一部地域・商品を除く)。</p>
</div>
</details>
<details class="accordion" name="demo">
<summary class="accordion__title">
<span>Q: 返品・交換は可能ですか?</span>
<span class="accordion__icon" aria-hidden="true"></span>
</summary>
<div class="accordion__content">
<p>商品到着後14日以内であれば、未開封・未使用に限り返品・交換を承ります。返品をご希望の場合は、カスタマーサポートまでご連絡ください。</p>
</div>
</details>
<details class="accordion" name="demo">
<summary class="accordion__title">
<span>Q: 支払い方法は何が使えますか?</span>
<span class="accordion__icon" aria-hidden="true"></span>
</summary>
<div class="accordion__content">
<p>クレジットカード(VISA、Mastercard、JCB、American Express)、銀行振込、コンビニ決済、各種電子マネーがご利用いただけます。</p>
<p>また、10,000円以上のご注文で分割払いもお選びいただけます。ご利用の際は決済画面で支払い回数をご指定ください。</p>
</div>
</details>
</div>
/* ----- details ----- */
.accordions {
--bdc: #d7dee5;
--duration: 0.3s;
border-block: solid 1px var(--bdc);
}
.accordion {
/* icon 変化用の変数 */
--icon-rotate: 0deg;
&[open] {
--icon-rotate: 90deg;
}
& + & {
border-top: 1px solid var(--bdc);
}
/*
::details-content をサポートしているブラウザでは開閉アニメーションをつける ( 非サポートブラウザでは無視される )
*/
&::details-content {
display: grid;
transition-duration: var(--duration);
transition-property: content-visibility, grid-template;
transition-behavior: allow-discrete;
/* 閉じてる状態 */
grid-template-rows: 0fr;
}
&[open]::details-content {
/* 開いている時 */
grid-template-rows: 1fr;
}
}
.accordion__title {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1em;
cursor: pointer;
padding: 1em 0.5em;
/* Safariで表示されるデフォルトの三角形アイコンを削除 */
&::-webkit-details-marker {
display: none;
}
}
/* タイトルホバー時、背景色を少し濃くする */
@media (any-hover: hover) {
.accordion:has(> .accordion__title:hover) {
background-color: color-mix(in srgb, var(--bdc), transparent 60%);
}
}
/* Tabキーでのフォーカス時も同じスタイリング */
.accordion:has(> .accordion__title:focus-visible) {
background-color: color-mix(in srgb, var(--bdc), transparent 60%);
}
/* アイコン */
.accordion__icon {
display: grid;
width: 1em;
height: 1em;
place-items: center;
&::before,
&::after {
content: '';
display: block;
grid-area: 1 / 1;
background-color: currentColor;
}
&::before {
width: 0.125em;
height: 100%;
transition: rotate var(--duration);
rotate: var(--icon-rotate);
}
&::after {
height: 0.125em;
width: 100%;
}
}
.accordion__content {
padding-inline: 0.5em;
overflow: hidden;
transition: padding-block var(--duration);
transition-duration: var(--duration);
[open] > & {
padding-bottom: 1em;
}
}
::details-contentに対してgrid-template-rowsを0fr~1frへトランジションさせる.
transition-property: content-visibility & transition-behavior: allow-discreteの指定を忘れないように.
- また、その子要素に対して
overflow: hiddenが必要なので、コンテンツはdiv(.accordion__content)でラップする.
::details-contentを使うのは、まだあくまでアニメーション部分だけ(非サポートブラウザではただアニメーションがなくなるだけですむように。)
- コンテンツに
paddingをつける時は.accordion__contentにつけて、open属性のない時はpadding-blockを0するようにする。もしくは、さらにdivでラップしてその中につける。
Can I use でcontent-visibility: Transitionable when setting transition-behavior: allow-discreteを確認するとFirefoxではサポートされていないことになっていますが、手元のFirefox v.145.0.2 では動作していました。
JS + CSSで開閉アニメーションを実装する例
Safariもサポートしつつアニメーションを実装するなら、JSの力を借りましょう。
スライドアニメーションは先ほどと同じ0fr~1frへのトランジションを使いますが、アニメーショントリガー用のdata-opened属性をJSでつけ外しするような実装をする例を紹介します。
ポイントは、open属性とタイミングをずらしてdata属性をつけ外しすることです。
<div class="accordions" data-multiple="disallow">
<details class="accordion">
<summary class="accordion__title">
<span>Q: 配送にはどのくらいかかりますか?</span>
</summary>
<div class="accordion__body">
<div class="accordion__content">
<p>ご注文から通常3〜5営業日でお届けします。お届け先の地域や在庫状況によって、お届けまでの日数が前後する場合がございます。</p>
<p>お急ぎの場合は、翌日配送オプションもご利用いただけます(一部地域・商品を除く)。</p>
</div>
</div>
</details>
<details class="accordion">
<summary class="accordion__title">
<span>Q: 返品・交換は可能ですか?</span>
</summary>
<div class="accordion__body">
<div class="accordion__content">
<p>
商品到着後14日以内であれば、未開封・未使用に限り返品・交換を承ります。返品をご希望の場合は、カスタマーサポートまでご連絡ください。
</p>
</div>
</div>
</details>
<details class="accordion">
<summary class="accordion__title">
<span>Q: 支払い方法は何が使えますか?</span>
</summary>
<div class="accordion__body">
<div class="accordion__content">
<p>クレジットカード(VISA、Mastercard、JCB、American Express)、銀行振込、コンビニ決済、各種電子マネーがご利用いただけます。</p>
<p>また、10,000円以上のご注文で分割払いもお選びいただけます。ご利用の際は決済画面で支払い回数をご指定ください。</p>
</div>
</div>
</details>
</div>
.accordions {
--duration: 0.3s;
--bdc: #d7dee5;
border-block: solid 1px var(--bdc);
}
.accordion {
& + & {
border-top: 1px solid var(--bdc);
}
}
.accordion__title {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1em;
cursor: pointer;
padding: 1em 0.5em;
/* Safariで表示されるデフォルトの三角形アイコンを削除 */
&::-webkit-details-marker {
display: none;
}
/* アイコン擬似要素 */
&::after {
content: '';
display: block;
width: 1em;
height: 1em;
background-color: currentColor;
/* clip-path で > アイコンを描画し、回転させる */
clip-path: polygon(
calc(35% + 4%) calc(10% - 4%),
calc(75% + 4% * 2) 50%,
calc(35% + 4%) calc(90% + 4%),
calc(35% - 4%) calc(90% - 4%),
calc(75% - 4% * 2) 50%,
calc(35% - 4%) calc(10% + 4%)
);
rotate: 90deg;
transition: scale 0.3s;
}
/* アコーディオンが開いている時、アイコンを反転 */
[data-opened] > &::after {
scale: -1 1;
}
}
/* タイトルホバー時、背景色を少し濃くする */
@media (any-hover: hover) {
.accordion:has(> .accordion__title:hover) {
background-color: color-mix(in srgb, var(--bdc), transparent 60%);
}
}
/* Tabキーでのフォーカス時も同じスタイリング */
.accordion:has(> .accordion__title:focus-visible) {
background-color: color-mix(in srgb, var(--bdc), transparent 60%);
}
.accordion__body {
display: grid;
transition-property: padding-block, grid-template;
transition-duration: var(--duration);
/* 閉じてる状態 */
grid-template-rows: 0fr;
padding-block: 0;
padding-inline: 0.5em;
}
/* ※ grid アニメーションに 必須 */
.accordion__content {
overflow: hidden;
}
/* 開いている時 */
[data-opened] > .accordion__body {
grid-template-rows: 1fr;
padding-bottom: 1em;
}
// アニメーションが完了するのを待つ
const waitAnimation = (element) => {
// アニメーションを取得 (子要素がアニメーションするので subtreeをtrueにセットする)
const animations = element.getAnimations({ subtree: true });
// allSettled を使うことで、キャンセル時も reject せずに完了する
return Promise.allSettled(animations.map((a) => a.finished));
};
const open = async (details) => {
// すでに開いている場合は何もしない
if (details.open && details.hasAttribute('data-opened')) return;
// open属性をセット
details.open = true;
// 次フレームで data-opened 属性を付与(CSS側でフェードインアニメーション開始)
requestAnimationFrame(() => {
details.setAttribute('data-opened', ''); // 属性の追加
});
};
const close = async (details) => {
// すでに閉じている場合は何もしない
if (!details.open && !details.hasAttribute('data-opened')) return;
details.removeAttribute('data-opened'); // 属性を削除
// アニメーションを待つ
await waitAnimation(details);
// アニメーション完了後にopen属性 を除去。
details.open = false;
};
const setEvent = (details) => {
// summary 要素をトリガーとする
const trigger = details.querySelector('summary');
if (!trigger) return;
// 親要素を取得
const parent = details.parentNode;
// 複数展開を許可するかどうかを、親要素の [data-multiple] でチェックする。(name属性での開閉はアニメーションが効かない)
let allowMultiple = false;
if (null != parent) {
allowMultiple = 'disallow' !== parent.getAttribute('data-multiple');
}
// summary の 'click' イベント
trigger.addEventListener('click', onClick);
function onClick(e) {
// すぐに open 属性が切り替わらないようにする
e.preventDefault();
// 開く処理
if (!details.open) {
// (複数展開が禁止されている場合)他の開いているアイテムがあるかどうかを先に探して閉じる
if (!allowMultiple) {
const openedItem = parent.querySelector(`[data-opened]`);
requestAnimationFrame(() => {
// 1フレーム待機(safariでは requestAnimationFrame() がないと動かなかった)
if (null != openedItem) close(openedItem);
});
}
open(details);
}
// 閉じる処理
else if (details.open) {
close(details);
}
}
// ページ内検索時にopenだけ付与されてしまうので、'toggle'イベントで属性操作する
// toggleイベントはclick時やname属性による開閉処理でも発火することに注意。また、e.preventDefault() は効かない。)
details.addEventListener('toggle', onToggle);
function onToggle(e) {
const hasOpen = details.open;
const hasDataOpen = details.hasAttribute('data-opened');
// open はセットされたのに data-opened 属性がついてない時
if (hasOpen && !hasDataOpen) {
details.setAttribute('data-opened', '');
}
// open は削除されたのに data-opened 属性がまだついている時
if (!hasOpen && hasDataOpen) {
details.removeAttribute('data-opened');
}
}
};
/**
* 処理の実行
*/
(function () {
const detailsAll = document.querySelectorAll('.accordion');
detailsAll.forEach((details) => {
setEvent(details);
});
})();
この実装の問題点
name属性を使って同一グループ内でのアコーディオン開閉を一つに制限すると、アニメーションがうまく動作しなくなります。
なので、それが気になる場合は独自実装が必要です。
上記の例では、data-multipleという属性を使って独自実装しています。
- さらに、ページ内検索時の開閉アニメーションも完全には動作しません。
これらの開閉動作はtoggleイベントでopen属性の切り替え後にしか検知できないためです。
JSで開閉アニメーションを実装する例
最後に、一応アニメーションをフルでJSに任せた実装例を紹介します。
<div class="accordions" data-multiple="disallow">
<details class="accordion">
<summary class="accordion__title">
<span>Q: 配送にはどのくらいかかりますか?</span>
</summary>
<div class="accordion__content">
<p>ご注文から通常3〜5営業日でお届けします。お届け先の地域や在庫状況によって、お届けまでの日数が前後する場合がございます。</p>
<p>お急ぎの場合は、翌日配送オプションもご利用いただけます(一部地域・商品を除く)。</p>
</div>
</details>
<details class="accordion">
<summary class="accordion__title">
<span>Q: 返品・交換は可能ですか?</span>
</summary>
<div class="accordion__content">
<p>商品到着後14日以内であれば、未開封・未使用に限り返品・交換を承ります。返品をご希望の場合は、カスタマーサポートまでご連絡ください。</p>
</div>
</details>
<details class="accordion">
<summary class="accordion__title">
<span>Q: 支払い方法は何が使えますか?</span>
</summary>
<div class="accordion__content">
<p>クレジットカード(VISA、Mastercard、JCB、American Express)、銀行振込、コンビニ決済、各種電子マネーがご利用いただけます。</p>
<p>また、10,000円以上のご注文で分割払いもお選びいただけます。ご利用の際は決済画面で支払い回数をご指定ください。</p>
</div>
</details>
</div>
.accordions {
}
/* border を使うとjsでタイトルの高さ取得した時に少し数値がずれてバウンドすることに注意 */
.accordion {
overflow: hidden;
+ .accordion {
margin-top: 1px;
}
}
/* display:list-item 以外にしてデフォルトの三角アイコンを非表示にする */
.accordion__title {
--bgc: #101010;
display: flex;
align-items: center;
justify-content: space-between;
padding: 1em;
background: var(--bgc);
color: #fff;
cursor: pointer;
/* Safariで表示されるデフォルトの三角形アイコンを削除 */
&::-webkit-details-marker {
display: none;
}
/* アイコン擬似要素 */
&::after {
content: '';
display: block;
width: 1em;
height: 1em;
background-color: currentColor;
/* clip-path で > アイコンを描画し、回転させる */
clip-path: polygon(
calc(35% + 4%) calc(10% - 4%),
calc(75% + 4% * 2) 50%,
calc(35% + 4%) calc(90% + 4%),
calc(35% - 4%) calc(90% - 4%),
calc(75% - 4% * 2) 50%,
calc(35% - 4%) calc(10% + 4%)
);
rotate: 90deg;
transition: rotate 0.3s;
}
.is-opened > &::after {
rotate: -90deg;
}
}
/* タイトルホバー時、背景色を少し半透明にする */
@media (any-hover: hover) {
.accordion__title:hover {
background-color: color-mix(in srgb, var(--bgc), transparent 15%);
}
}
/* Tabキーでのフォーカス時も同じスタイリング */
.accordion__title:focus-visible {
background-color: color-mix(in srgb, var(--bgc), transparent 15%);
}
.accordion__content {
padding: 1.25em;
background-color: #f2f2f2;
}
(function () {
const ANIMATION_TIMES = {
duration: 250,
easing: 'ease-out',
};
const accordions = document.querySelectorAll('.accordion');
accordions.forEach((accordion) => {
// アニメーションを保持しておくための変数
let animation = null;
// 現在のアニメーション状況を保持する変数
let nowAnimation = '';
// 親要素を取得
const parent = accordion.parentNode;
// 複数展開を許可するかどうかを、親要素の [data-multiple] でチェックする
let allowMultiple = false;
if (null != parent) {
allowMultiple = 'disallow' !== parent.getAttribute('data-multiple');
}
// アニメーションリセット時の処理
const resetAnimation = () => {
animation = null;
nowAnimation = '';
accordion.style.height = '';
};
// __title 取得
const title = accordion.querySelector('.accordion__title');
// __title の高さを取得
const titleHeight = title.offsetHeight;
// __content 取得
const content = accordion.querySelector('.accordion__content');
title.addEventListener('click', (e) => {
// すぐに open 属性が切り替わらないようにする
e.preventDefault();
// .accordion 全体の高さを取得(アニメーション中であればその高さを取得したいので、キャンセル前に取得)
const accordionHeight = accordion.offsetHeight;
// アニメーション中であればキャンセルさせる
if (animation) {
animation.cancel();
}
// オープン / クローズ 処理
if (nowAnimation === 'closing' || !accordion.open) {
// open属性を最初にセット
accordion.open = true;
// 現在の高さで一旦固定させる
accordion.style.height = `${accordionHeight}px`;
// コンテンツの高さを取得。(openを付けたあとで取得しないと iOSで0になる)
const contentHeight = content.offsetHeight;
// アニメーションの状況をセット
nowAnimation = 'opening';
// 現在の高さから __title + コンテンツ の高さまで広げる
animation = accordion.animate(
{
height: `${titleHeight + contentHeight}px`,
},
ANIMATION_TIMES
);
// アニメーション完了時の処理を登録
animation.onfinish = () => {
accordion.open = true;
resetAnimation();
};
// アイコンのアニメーション用クラスの切り替え(これもタイミングに気をつけないと動かないときがある)
accordion.classList.add('is-opened');
} else if (nowAnimation === 'opening' || accordion.open) {
// (複数展開が禁止されている場合)他の開いているアイテムがあるかどうかを先に探して閉じる
if (!allowMultiple && parent) {
const openedItem = parent.querySelector('.is-opened');
if (openedItem && openedItem !== accordion) {
const summary = openedItem.querySelector('.accordion__title');
if (summary) summary.click();
}
}
// 現在の高さで一旦固定させる
accordion.style.height = `${accordionHeight}px`;
// アニメーションの状況をセット
nowAnimation = 'closing';
// 現在の高さから __title の高さまで縮める
animation = accordion.animate(
{
height: `${titleHeight}px`,
},
ANIMATION_TIMES
);
// アニメーション完了時の処理を登録
animation.onfinish = () => {
accordion.open = false;
resetAnimation();
};
// アイコンのアニメーション用クラスを削除
accordion.classList.remove('is-opened');
}
});
});
})();
details/summary でのアニメーションに関しての基本的なことについては、ICSメディアさんの記事が非常に参考になりました。
それを元に、もう少しpaddingの融通が効くように調整を加えています。(コンテンツではなくdetailsのheightをいじるようにした)
まとめ
本ページでは、detailsとsummary要素を使用したアコーディオンの実装方法を解説しました。
details/summary要素を使えば、JavaScriptなしでアクセシビリティの高いアコーディオンが実装できるだけでなく、開閉時のスライドアニメーションについても、CSSだけで実装できるようになっています。
参考リンク
CSSでheight: autoでもアニメーションが可能に! interpolate-sizeとは - ICS MEDIA UIのインタラクションの実装で、height: 0 → autoなど、数値とキーワード値とをアニメーションさせたいと思ったことはないでしょうか。一見可能そうに見えるものの、従来はCSSのみではアニメーションが不可能でした。
ICS MEDIA
detailsとsummaryタグで作るアコーディオンUI - アニメーションのより良い実装方法 - ICS MEDIA アコーディオン型ユーザーインターフェイス(UI)はウェブページでよくみられる表現です。巷ではさまざまな方法でアコーディオンUIを作る方法が紹介されていますが、みなさんはどのような方法で実装していますか。
ICS MEDIA
details要素に命を吹き込む::details-content擬似要素の登場 details要素のコンテンツ部分にスタイルを適用できる::details-content擬似要素がBaseline 2025に加わりました。details要素を用いたスタイリングの守備範囲が広がっただけではなく、アニメーションの適用も容易になりました。
k8o
CSSのみでdetails要素のアニメーションを実装する方法 – TAKLOG
www.tak-dcxi.com
アクセシブルなアコーディオンの実装について考える
Zenn