- 技術 💻
detailsアコーディオンをCSSのみで|開閉アニメーション・name排他
details 要素とは、クリックで中身を開閉できる「折りたたみ」を HTML だけで作れる要素です。 見出しにあたる部分を summary に書き、残りが開閉される中身になります。JavaScript を書かなくても、キーボード操作やスクリーンリーダーへの対応、ページ内検索での自動展開がブラウザ側で用意されています。
この記事では、name 属性で「1つだけ開く」アコーディオンにする方法、最新 CSS(interpolate-size・::details-content)で高さをなめらかに動かす方法、未対応ブラウザでも崩れない版、閉じるときもアニメーションする JavaScript 最小版の3つをコピペできる形でまとめます。各機能の対応状況は MDN の互換性データ(@mdn/browser-compat-data 8.1.4、2026年10月1日更新)で確認し、Playwright 1.56.1+Chromium 141 で開閉・排他・高さの推移・テキスト検索での自動展開を実際に動かした結果も載せています。
details アコーディオンの早見表
やりたいことから、使う機能と書き方を引ける一覧です。
| やりたいこと | 書き方 | 対応状況の目安 |
|---|---|---|
| 開閉する箱を作る | <details><summary>質問</summary>答え</details> |
全ブラウザ |
| 最初から開いておく | <details open> |
全ブラウザ |
| 1つだけ開く(排他) | 同じグループに name="faq" を付ける |
Chrome/Edge 120、Firefox 130、Safari 17.2 以降 |
| 中身部分だけにスタイル | details::details-content |
Chrome/Edge 131、Firefox 143、Safari 18.4 以降 |
| height: auto へアニメーション | :root { interpolate-size: allow-keywords; } |
Chrome/Edge 129 以降(Firefox・Safari は未対応) |
| 閉じるときも中身を見せたまま動かす | transition: content-visibility .3s allow-discrete |
Chrome/Edge 117、Firefox 129、Safari 17.4 以降 |
| 三角マーカーを消す | summary { list-style: none; } + summary::-webkit-details-marker { display: none; } |
全ブラウザ |
| +/− や矢印のアイコン | summary::after と details[open] で切り替え |
全ブラウザ |
| 開閉を検知する | toggle イベント |
全ブラウザ |
| ページ内検索で自動で開く | details は標準の動作(設定不要) | Chrome/Edge 97、Firefox 148 以降、Safari 26.2 以降(一部) |
| details 以外の要素を検索で開く | hidden="until-found" |
Chrome/Edge 102、Firefox 148、Safari 26.2 以降 |
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「detailsアコーディオンをCSSのみで|開閉アニメーション・name排他」
details と summary の基本と open 属性
details の最初の子要素に summary を置くと、summary がクリックできる見出しになり、それ以外が開閉される中身になります。
<details>
<summary>送料はいくらですか?</summary>
<p>全国一律で500円です。5,000円以上のご注文で無料になります。</p>
</details>
| 要素・属性 | 役割 |
|---|---|
<details> |
開閉する箱全体 |
<summary> |
常に見えている見出し部分。クリック・Enter・Space で開閉 |
open 属性 |
付いていると開いた状態。ユーザーが開閉すると自動で付け外しされる |
toggle イベント |
開閉のあとに発生。e.newState で "open"/"closed" を受け取れる |
open 属性は「状態」そのものなので、CSS では details[open] で開いているときだけのスタイルを書けます。JavaScript では details.open = true で開けます。
summary を書き忘れると、ブラウザが「詳細」などの既定の文字を表示します。質問文やラベルは必ず summary に書きます。
name 属性で「1つだけ開く」アコーディオンにする
同じ name を付けた details は、1つを開くと他が自動で閉じます。 ラジオボタンと同じ考え方で、JavaScript は不要です。
<details name="faq">
<summary>送料はいくらですか?</summary>
<p>全国一律で500円です。</p>
</details>
<details name="faq">
<summary>返品はできますか?</summary>
<p>到着から7日以内であれば返品できます。</p>
</details>
<details name="faq">
<summary>領収書は出せますか?</summary>
<p>マイページから発行できます。</p>
</details>
Chromium 141 で、A → B → B の順にクリックしたときの状態と toggle イベントの記録です。
初期 a=closed b=closed c=closed
A をクリック a=open b=closed c=closed
B をクリック a=closed b=open c=closed
B を再クリック a=closed b=closed c=closed
toggle イベント a:closed->open, b:closed->open, a:open->closed, b:open->closed
B を開いた時点で A が自動で閉じ、閉じた A にも toggle イベントが発生しています。 また、開いているものをもう一度クリックすると全部閉じた状態にできます(「必ず1つは開いている」動きにはなりません)。
| 押さえておきたい点 | 内容 |
|---|---|
| グループの単位 | 同じ文書内で name が同じもの(フォームの name とは別物) |
| 未対応ブラウザ | name が無視され、複数同時に開けるだけで表示は崩れない |
HTML に open を複数書いた場合 |
排他になるよう、最初の1つ以外は閉じた状態で表示される |
JS で open = true にした場合 |
同じ name の他の details は閉じる |
表の下2行と、印刷時などに name を外す手順も Chromium 141 で確かめました。
HTMLにopen複数 a=true b=false c=false
JSで全部open a=false b=false c=true
name外して全部open a=true b=true c=true
HTML で複数に open を書くと最初の1つだけが開き、JS で順に開くと最後の1つだけが残りました。 すべて開きたいときは name を外してから開きます。
複数の FAQ グループを1ページに置くときは、faq-shipping・faq-account のようにグループごとに name を変えます。
開閉アニメーションのコピペコード3種
どれを選ぶかの判断表を先に示します。
| 版 | 開く | 閉じる | 対応 | 向いている場面 |
|---|---|---|---|---|
| ① 最新 CSS 版 | 高さアニメ | 高さアニメ | Chrome/Edge 131 以降。他は即時開閉(崩れない) | CSS のみで済ませたい |
| ② 崩れない CSS 版 | フェードイン | 即時 | 全ブラウザ | 対応ブラウザを広く揃えたい |
| ③ JS 最小版 | 高さアニメ | 高さアニメ | 全ブラウザ | どのブラウザでも閉じる動きを揃えたい |
① 最新 CSS 版(interpolate-size・::details-content)
interpolate-size: allow-keywords を指定すると、0 から auto のようなキーワードへの高さアニメーションができます。 さらに ::details-content(details の中身部分を指す擬似要素)に高さの transition を付けます。
/* ① 最新 CSS 版:開く・閉じるとも高さをアニメーション */
@supports (interpolate-size: allow-keywords) {
:root {
interpolate-size: allow-keywords; /* auto への補間を許可(ページ全体に効く) */
}
.accordion details::details-content {
block-size: 0;
overflow-y: clip;
transition:
block-size 0.3s ease,
content-visibility 0.3s allow-discrete; /* 閉じる間も中身を表示し続ける */
}
.accordion details[open]::details-content {
block-size: auto;
}
}
@media (prefers-reduced-motion: reduce) {
.accordion details::details-content {
transition: none;
}
}
ポイントは content-visibility ... allow-discrete です。 閉じるとき、ブラウザは中身を content-visibility: hidden にしますが、これを transition に入れて allow-discrete(離散的な値も transition の終わりで切り替える指定)を付けると、高さが 0 になるまで中身が見えたままになります。これは transition-behavior: allow-discrete の省略記法です。
Chromium 141 で、開いた直後から一定間隔で details の高さを測った結果です(summary 部分が 45px、全部開いて 163px)。
CSS版 開く 0ms:45 60ms:71 120ms:124 180ms:146 240ms:160 330ms:163
CSS版 閉じる 0ms:163 60ms:137 120ms:84 180ms:62 240ms:49 330ms:45
開くときも閉じるときも、0.3秒かけて高さが連続的に変わりました。 interpolate-size に対応していないブラウザ(2026年10月時点の MDN では Firefox・Safari)では @supports の中身が使われず、通常の即時開閉になります。
:root に interpolate-size を指定すると、ページ全体で auto への補間が有効になります。影響範囲を絞りたい場合は .accordion { interpolate-size: allow-keywords; } のようにアコーディオンの親に指定します(継承されるプロパティなので子孫に効きます)。
② 未対応ブラウザでも崩れない版(フェードイン)
高さのアニメーションをあきらめ、開いたときに中身をふわっと表示する版です。 すべてのブラウザで同じように動きます。
/* ② 崩れない CSS 版:開くときだけフェードイン+少し下から */
.accordion details[open] > :not(summary) {
animation: acc-fade 0.3s ease;
}
@keyframes acc-fade {
from { opacity: 0; transform: translateY(-4px); }
to { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
.accordion details[open] > :not(summary) { animation: none; }
}
①と②は一緒に書けます。 ①が使えるブラウザでは高さアニメーション、使えないブラウザではフェードインになるように、②を @supports not (interpolate-size: allow-keywords) の中に入れると重複しません。
@supports not (interpolate-size: allow-keywords) {
.accordion details[open] > :not(summary) {
animation: acc-fade 0.3s ease;
}
}
③ 閉じるときもアニメーションする JS 最小版
どのブラウザでも閉じる動きまで揃えたい場合は、Web Animations API(element.animate())で高さを動かします。 閉じるときは「アニメーションが終わってから open を外す」のが要点です。
<div class="js-acc">
<details>
<summary>送料はいくらですか?</summary>
<p>全国一律で500円です。5,000円以上のご注文で無料になります。</p>
</details>
</div>
<style>
.js-acc details { overflow: hidden; } /* details 自身に padding・border を付けない */
.js-acc summary { padding: 12px 16px; cursor: pointer; }
.js-acc p { margin: 0; padding: 16px; }
</style>
<script>
document.querySelectorAll('.js-acc details').forEach((details) => {
const summary = details.querySelector('summary');
let anim = null;
let closing = false;
summary.addEventListener('click', (e) => {
if (matchMedia('(prefers-reduced-motion: reduce)').matches) return; // 標準の開閉に任せる
e.preventDefault();
const startH = details.offsetHeight;
const opening = !details.open || closing;
if (!details.open) details.open = true; // 開くときは先に open にして高さを測れるようにする
const endH = opening ? details.scrollHeight : summary.offsetHeight;
anim?.cancel();
closing = !opening;
anim = details.animate(
{ height: [`${startH}px`, `${endH}px`] },
{ duration: 300, easing: 'ease' }
);
anim.onfinish = () => {
if (closing) details.open = false; // 閉じるアニメーションが終わってから open を外す
closing = false;
anim = null;
};
});
});
</script>
Chromium 141 での高さの推移です。
JS版 開く 0ms:50 60ms:71 120ms:124 180ms:146 240ms:159 330ms:163
JS版 閉じる 0ms:163 60ms:137 120ms:84 180ms:62 240ms:49 330ms:45
JS版 閉じた後 open = false
reduce 時 即 open = true
閉じるアニメーションの後に open が外れ、「動きを減らす」設定(prefers-reduced-motion: reduce)では即時に開きました。 アニメーション中にもう一度クリックすると、anim.cancel() で途中から逆向きに動き直します。
| JS 版のつまずき | 理由 | 対処 |
|---|---|---|
| 閉じるアニメーションが見えない | 先に open を外すと中身が消える |
onfinish で open を外す |
| 高さが少しずれる | details に padding や border がある | 余白は summary と中身の要素に付ける |
| name の排他と組み合わせると他は即時に閉じる | 自動で閉じる側にはクリックが発生しない | 必要なら toggle イベントで閉じた側も処理する |
マーカーの消し方と +/− アイコン・回転矢印
三角マーカーを消す
summary の三角は ::marker(リスト項目の印)として表示されています。 Safari では独自の ::-webkit-details-marker も使われるため、両方を消します。
.accordion summary {
list-style: none; /* ::marker を消す */
cursor: pointer;
}
.accordion summary::-webkit-details-marker {
display: none; /* Safari の独自マーカーを消す */
}
summary に display: flex を指定してもマーカーは表示されません。 summary の既定の display は list-item で(Chromium 141 で確認)、仕様上 list-item のときだけマーカーが付くためです。アイコンを右端に寄せるために flex にする場合は、この性質を利用できます。
+/− アイコン
疑似要素2本の線で「+」を作り、開いたときに縦線を消して「−」にします。
.accordion summary {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
padding: 12px 16px;
}
.accordion summary::after {
content: "";
flex: none;
inline-size: 14px;
block-size: 14px;
background:
linear-gradient(currentColor 0 0) center / 100% 2px no-repeat, /* 横線 */
linear-gradient(currentColor 0 0) center / 2px 100% no-repeat; /* 縦線 */
transition: background-size 0.2s;
}
.accordion details[open] > summary::after {
background-size: 100% 2px, 2px 0; /* 縦線を 0 にして − にする */
}
回転する矢印(シェブロン)
.accordion summary::after {
content: "";
flex: none;
inline-size: 8px;
block-size: 8px;
border-right: 2px solid currentColor;
border-bottom: 2px solid currentColor;
transform: rotate(45deg); /* 下向き */
transition: transform 0.2s;
}
.accordion details[open] > summary::after {
transform: rotate(-135deg); /* 上向き */
}
| アイコン | 閉じているとき | 開いているとき | 向いている場面 |
|---|---|---|---|
| +/− | + | − | FAQ・用語集 |
| シェブロン | 下向き | 上向き | メニュー・設定項目 |
| テキスト | 「開く」 | 「閉じる」 | 文字で状態を伝えたい場合(details[open] summary .label を切り替え) |
アイコンは装飾なので、疑似要素(content: "")で描きます。 状態は summary の開閉としてブラウザが支援技術に伝えるため、アイコンに読み上げ用の文字は要りません。
各機能のブラウザ対応表(2026年10月時点)
MDN の互換性データ @mdn/browser-compat-data 8.1.4(2026-10-01 更新)から取り出した、各機能が使えるようになったバージョンです。
| 機能 | Chrome | Edge | Firefox | Safari | iOS Safari |
|---|---|---|---|---|---|
<details> / open |
12 | 79 | 49 | 6 | 6 |
name(排他) |
120 | 120 | 130 | 17.2 | 17.2 |
::marker |
86 | 86 | 68 | 11.1 | 11.3 |
transition-behavior(allow-discrete) |
117 | 117 | 129 | 17.4 | 17.4 |
::details-content |
131 | 131 | 143 | 18.4 | 18.4 |
interpolate-size |
129 | 129 | 未対応 | 未対応 | 未対応 |
| ページ内検索で details が開く | 97 | 97 | 148 | 26.2(一部) | 26.2(一部) |
hidden="until-found" |
102 | 102 | 148 | 26.2(一部) | 26.2(一部) |
beforematch イベント |
102 | 102 | 139 | 26.2 | 26.2 |
interpolate-size は MDN で「実験的」扱いで、Chrome/Edge のみ対応しています。 そのため①の最新 CSS 版は @supports で囲み、他のブラウザでは即時開閉になるようにしています。Safari 26.2 の「一部」は、MDN の注記によると「一致したテキストへのスクロールが正しく行われない」というものです。
ページ内検索で自動で開く挙動(hidden="until-found" 相当)
閉じた details の中の文字も、ブラウザのページ内検索(Ctrl/⌘ + F)で見つかると自動で開きます。 設定は不要で、details を使う大きな利点です。details 以外の要素で同じ動きをさせたいときは hidden="until-found" を使います。
<!-- details 以外の要素を「見つかったら表示」にする -->
<div hidden="until-found" id="note">
<p>補足:在庫は毎朝9時に更新されます。</p>
</div>
<script>
document.getElementById('note').addEventListener('beforematch', () => {
console.log('検索で見つかったので表示されます');
});
</script>
Ctrl/⌘ + F の操作は Playwright から行えないため、同じ「見つかったら開く」仕組みが働くテキストフラグメント(URL の #:~:text= で指定した文字へ移動する機能)で確認しました。 リンクをクリックして移動したときの結果です。
details の中の「ひみつのあいことば」へ移動 → b.open true / log b:closed->open
hidden="until-found" の中の文へ移動 → hidden 属性 null / log beforematch
閉じていた details が開いて toggle イベントが発生し、hidden="until-found" の要素は hidden 属性が外れて beforematch イベントが発生しました。 Ctrl/⌘ + F での動作そのものは、MDN の互換性データ(上の表)で確認した内容です。
確認するときの注意点もまとめておきます。
| 注意点 | 内容 |
|---|---|
| Playwright の既定の headless shell では動かなかった | chromium.launch({ channel: 'chromium' })(新しい headless)で確認できた |
| テキストフラグメントは語の区切りで照合する | 文の途中の一部だけを指定すると一致しないことがあった。文全体を指定すると一致した |
page.goto() で直接開くより、リンクのクリックで移動する |
クリックで移動した場合に動作した |
アクセシビリティで気をつけること
summary はブラウザによって「ボタン」として支援技術に伝えられます。 そのため、summary の中身はボタンのラベルとして読み上げられる、短い文にしておくのが基本です。
| 書き方 | おすすめ度 | 理由 |
|---|---|---|
<summary>送料はいくらですか?</summary> |
推奨 | ボタンのラベルとして自然に読み上げられる |
| 見出しを details の外に置き、summary は「詳しく見る」 | 推奨 | 見出しジャンプ(見出し単位の移動)でも確実に見つかる |
<summary><h3>送料</h3></summary> |
避ける | HTML の仕様上は書けるが、ボタンとして扱う読み上げ環境では見出しとして伝わらないことがある |
<summary>メニュー <button>編集</button></summary> |
避ける | ボタンの中にボタンがある入れ子になり、フォーカスが2回止まる・読み上げが分かりにくくなる |
Chromium 141 では、summary の中のボタンをクリックしても details は開閉しませんでした(ボタン側の処理だけが動きました)。動作としては分かれていても、操作する人には「どこを押すと何が起きるか」が分かりにくいため、編集ボタンなどは summary の外(中身の側)に置くとよいでしょう。
ほかに確認しておきたい点です。
- フォーカスの見た目を消さない:
summary:focus-visible { outline: 2px solid; outline-offset: 2px; }のように、キーボード操作時の枠を残します。 - 動きを減らす設定に合わせる:コード①〜③のように
prefers-reduced-motion: reduceでアニメーションを止めます。 - 開閉の状態はブラウザに任せる:details を使っていれば
aria-expandedを自分で書く必要はありません。div で作り直す場合は、button とaria-expanded・aria-controlsが必要になります。
よくある不具合と直し方
| 症状 | 原因 | 直し方 |
|---|---|---|
| 三角が Safari だけ消えない | ::-webkit-details-marker が残っている |
summary::-webkit-details-marker { display: none; } を追加 |
| height の transition が効かない | auto への補間ができない |
interpolate-size: allow-keywords を指定(対応ブラウザのみ)/JS 版を使う |
| 閉じるときだけ一瞬で消える | content-visibility がすぐ切り替わる | content-visibility .3s allow-discrete を transition に追加 |
| 中身の余白(margin)がはみ出してカクつく | 子要素の margin が高さ計算の外に出る | 中身の余白は padding で付ける/overflow-y: clip |
| name を付けても排他にならない | name の綴りが違う/未対応ブラウザ | 同じ文字列にする。未対応でも表示は崩れない |
| JS で開いたのに他が閉じない | name が付いていない | 排他にしたい details すべてに同じ name を付ける |
| 印刷すると閉じた中身が出ない | 閉じた状態のまま印刷される | beforeprint イベントで open = true にする |
印刷時に全部開く例です。
window.addEventListener('beforeprint', () => {
document.querySelectorAll('details').forEach((d) => { d.open = true; });
});
name で排他にしている場合、全部を開くと最後の1つ以外が閉じるため(上の確認結果のとおり)、印刷用には一時的に removeAttribute('name') してから開きます。
なお、画面全体を覆うモーダルやダイアログを作りたい場合は details ではなく dialog 要素が向いています。dialog については「HTML dialog要素でモーダル実装」の記事で扱っています。
よくある質問
Q. アコーディオンは CSS のみで作れますか?
A. 作れます。details・summary で開閉、name 属性で排他、::details-content と interpolate-size で高さのアニメーションまで CSS と HTML だけでできます。高さのアニメーションは2026年10月時点で Chrome/Edge のみ対応で、他のブラウザでは即時開閉になります。
Q. 閉じるときのアニメーションが効きません。
A. 閉じるときは中身がすぐ非表示になるため、transition に content-visibility .3s allow-discrete を加える必要があります。全ブラウザで揃えたい場合は、onfinish で open を外す JS 版を使います。
Q. 最初の1つだけ開いた状態にするには?
A. 最初の details に open 属性を書きます。 name で排他にしていても、ユーザーが別の項目を開くと自動で閉じます。
Q. FAQ の構造化データと一緒に使えますか? A. 使えます。details で開閉していても中身は HTML に含まれているため、ページ内の質問と回答を JSON-LD の FAQPage として記述できます(表示内容と一致させることが前提です)。
まとめ
- details と summary だけで、キーボード操作とページ内検索に対応した開閉部品が作れます。
- 同じ name 属性を付けると「1つだけ開く」アコーディオンになり、JavaScript は不要です。
- 最新 CSS 版は
interpolate-sizeと::details-content、content-visibility ... allow-discreteで開閉とも高さが動きます(Chrome/Edge)。 - 未対応ブラウザには
@supportsでフェードイン版を、全ブラウザで閉じる動きを揃えるなら JS 最小版を使います。 - マーカーは
list-style: noneと::-webkit-details-markerの両方で消し、アイコンは疑似要素で描きます。 - summary の中に見出しやボタンを入れず、短いラベルにすると読み上げが自然になります。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「detailsアコーディオンをCSSのみで|開閉アニメーション・name排他」
株式会社RJCでは、HTML 標準の要素を活かした使いやすい UI づくりを仲間と学び合っています。こうした知識を共有しながら、日々技術を磨いています。
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。