アコーディオン

見出しのクリックで本文を滑らかに開閉するアコーディオン。max-heightアニメとaria属性で、FAQやヘルプの折りたたみ表示に使えます。

#css#javascript#animation#accessibility

ライブデモ

使用例(お題: カフェ MOON BREW)

この技法を「カフェ MOON BREW」というテーマのダミーサイトで実際に使った例です。

HTML
<!-- MOON BREW:店舗FAQをアコーディオンで折りたたみ表示 -->
<div class="cafe">
  <header class="cafe__bar">
    <span class="cafe__logo">☕ MOON BREW</span>
    <nav class="cafe__nav"><a>MENU</a><a>STORE</a><a class="is-active">FAQ</a></nav>
  </header>

  <div class="cafe__faq">
    <p class="cafe__eyebrow">よくあるご質問</p>
    <h1 class="cafe__h1">ご来店の前に</h1>

    <div class="acc">
      <div class="acc__item" data-acc>
        <button class="acc__head" aria-expanded="false">
          <span>席の予約はできますか?</span>
          <svg class="acc__icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M6 9l6 6 6-6"/></svg>
        </button>
        <div class="acc__panel"><p>2名様以上から公式サイトで前日までご予約いただけます。窓際のソファ席は人気のため、お早めの予約がおすすめです。</p></div>
      </div>

      <div class="acc__item" data-acc>
        <button class="acc__head" aria-expanded="false">
          <span>豆の量り売りはありますか?</span>
          <svg class="acc__icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M6 9l6 6 6-6"/></svg>
        </button>
        <div class="acc__panel"><p>自家焙煎の中深煎り「ムーンブレンド」を100g単位で量り売りしております。挽き方もその場でお選びいただけます。</p></div>
      </div>

      <div class="acc__item" data-acc>
        <button class="acc__head" aria-expanded="false">
          <span>Wi-Fiや電源は使えますか?</span>
          <svg class="acc__icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M6 9l6 6 6-6"/></svg>
        </button>
        <div class="acc__panel"><p>全席で無料Wi-Fiをご利用いただけます。電源はカウンター席とテラス席にご用意。混雑時は2時間制とさせていただきます。</p></div>
      </div>
    </div>
  </div>
</div>
CSS
/* MOON BREW カフェ テーマ */
:root{
  --cream:#f5ede1;
  --brown:#2b1d12;
  --amber:#c98a3b;
  --line:#e3d6c2;
  --muted:#7a6450;
}
*{box-sizing:border-box}
body{
  margin:0;min-height:100vh;
  font-family:"Hiragino Mincho ProN","Segoe UI",serif;
  background:var(--cream);color:var(--brown);
}
.cafe{max-width:560px;margin:0 auto;padding:0 18px 24px}
.cafe__bar{
  display:flex;align-items:center;justify-content:space-between;
  padding:14px 4px;border-bottom:1px solid var(--line);
}
.cafe__logo{font-weight:700;letter-spacing:.06em}
.cafe__nav{display:flex;gap:14px;font-size:.78rem;letter-spacing:.1em;color:var(--muted)}
.cafe__nav a{cursor:pointer}
.cafe__nav .is-active{color:var(--amber);font-weight:700}
.cafe__faq{padding-top:18px}
.cafe__eyebrow{margin:0;font-size:.72rem;letter-spacing:.25em;color:var(--amber)}
.cafe__h1{margin:4px 0 16px;font-size:1.5rem;letter-spacing:.04em}
.acc{width:100%}
.acc__item{
  background:#fffaf2;border:1px solid var(--line);
  border-radius:12px;margin-bottom:10px;overflow:hidden;
  transition:border-color .25s,box-shadow .25s;
}
.acc__item.is-open{border-color:var(--amber);box-shadow:0 6px 18px rgba(201,138,59,.12)}
.acc__head{
  width:100%;display:flex;align-items:center;justify-content:space-between;gap:12px;
  padding:15px 18px;background:none;border:none;cursor:pointer;
  color:inherit;font:inherit;font-weight:600;text-align:left;
}
.acc__head:focus-visible{outline:2px solid var(--amber);outline-offset:-2px}
.acc__icon{
  width:18px;height:18px;flex:none;fill:none;stroke:var(--amber);
  stroke-width:2.2;stroke-linecap:round;stroke-linejoin:round;
  transition:transform .3s ease;
}
.acc__item.is-open .acc__icon{transform:rotate(180deg)}
/* max-height アニメで滑らかに開閉 */
.acc__panel{max-height:0;overflow:hidden;transition:max-height .35s ease}
.acc__panel p{margin:0;padding:0 18px 16px;color:var(--muted);line-height:1.75;font-size:.9rem}
@media (prefers-reduced-motion:reduce){.acc__panel,.acc__icon{transition:none}}
JavaScript
// 各FAQ項目に開閉トグルを設定(1つだけ開くモード)
const items = document.querySelectorAll('[data-acc]');

items.forEach((item) => {
  const head = item.querySelector('.acc__head');
  const panel = item.querySelector('.acc__panel');
  if (!head || !panel) return; // null安全

  head.addEventListener('click', () => {
    const willOpen = !item.classList.contains('is-open');

    // すべて閉じてから対象だけ開く
    items.forEach((other) => {
      other.classList.remove('is-open');
      const oh = other.querySelector('.acc__head');
      const op = other.querySelector('.acc__panel');
      if (oh) oh.setAttribute('aria-expanded', 'false');
      if (op) op.style.maxHeight = null;
    });

    if (willOpen) {
      item.classList.add('is-open');
      head.setAttribute('aria-expanded', 'true');
      panel.style.maxHeight = panel.scrollHeight + 'px'; // 実高さで展開
    }
  });
});

コード

HTML
<!-- アコーディオン:見出しをクリックすると本文が開閉する -->
<div class="acc">
  <h2 class="acc__title">よくある質問</h2>

  <div class="acc__item" data-acc>
    <button class="acc__head" aria-expanded="false">
      <span>配送にはどれくらいかかりますか?</span>
      <svg class="acc__icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M6 9l6 6 6-6"/></svg>
    </button>
    <div class="acc__panel"><p>ご注文確定後、通常2〜4営業日でお届けします。離島・一部地域は追加日数をいただく場合があります。</p></div>
  </div>

  <div class="acc__item" data-acc>
    <button class="acc__head" aria-expanded="false">
      <span>返品・交換はできますか?</span>
      <svg class="acc__icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M6 9l6 6 6-6"/></svg>
    </button>
    <div class="acc__panel"><p>到着後14日以内であれば未使用品に限り返品・交換を承ります。お問い合わせフォームからご連絡ください。</p></div>
  </div>

  <div class="acc__item" data-acc>
    <button class="acc__head" aria-expanded="false">
      <span>支払い方法を教えてください</span>
      <svg class="acc__icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M6 9l6 6 6-6"/></svg>
    </button>
    <div class="acc__panel"><p>各種クレジットカード、コンビニ決済、銀行振込、主要なQRコード決済に対応しています。</p></div>
  </div>
</div>
CSS
/* カラーとサイズをカスタムプロパティで管理 */
:root{
  --bg:#0f172a;
  --card:#1e293b;
  --line:#334155;
  --accent:#38bdf8;
  --text:#e2e8f0;
  --muted:#94a3b8;
  --radius:14px;
}
*{box-sizing:border-box}
body{
  margin:0;
  min-height:100vh;
  display:grid;
  place-items:center;
  padding:28px 16px;
  font-family:"Segoe UI",system-ui,sans-serif;
  color:var(--text);
  background:radial-gradient(1200px 500px at 50% -10%,#1e3a5f,transparent),var(--bg);
}
.acc{width:min(440px,100%)}
.acc__title{
  margin:0 0 16px;
  font-size:1.15rem;
  letter-spacing:.04em;
  display:flex;align-items:center;gap:10px;
}
.acc__title::before{
  content:"";width:8px;height:22px;border-radius:4px;
  background:linear-gradient(var(--accent),#6366f1);
}
.acc__item{
  background:var(--card);
  border:1px solid var(--line);
  border-radius:var(--radius);
  margin-bottom:10px;
  overflow:hidden;
  transition:border-color .25s;
}
.acc__item.is-open{border-color:var(--accent)}
.acc__head{
  width:100%;
  display:flex;align-items:center;justify-content:space-between;gap:12px;
  padding:16px 18px;
  background:none;border:none;cursor:pointer;
  color:inherit;font:inherit;font-weight:600;text-align:left;
}
.acc__head:focus-visible{outline:2px solid var(--accent);outline-offset:-2px}
.acc__icon{
  width:20px;height:20px;flex:none;
  fill:none;stroke:var(--accent);stroke-width:2.2;stroke-linecap:round;stroke-linejoin:round;
  transition:transform .3s ease;
}
.acc__item.is-open .acc__icon{transform:rotate(180deg)}
/* max-height アニメで滑らかに開閉 */
.acc__panel{
  max-height:0;overflow:hidden;
  transition:max-height .35s ease;
}
.acc__panel p{
  margin:0;padding:0 18px 18px;
  color:var(--muted);line-height:1.7;font-size:.92rem;
}
@media (prefers-reduced-motion:reduce){
  .acc__panel,.acc__icon{transition:none}
}
JavaScript
// 各アコーディオン項目に開閉トグルを設定
const items = document.querySelectorAll('[data-acc]');

items.forEach((item) => {
  const head = item.querySelector('.acc__head');
  const panel = item.querySelector('.acc__panel');
  if (!head || !panel) return; // null安全

  head.addEventListener('click', () => {
    const willOpen = !item.classList.contains('is-open');

    // 他の項目を閉じる(1つだけ開くモード)
    items.forEach((other) => {
      other.classList.remove('is-open');
      const oh = other.querySelector('.acc__head');
      const op = other.querySelector('.acc__panel');
      if (oh) oh.setAttribute('aria-expanded', 'false');
      if (op) op.style.maxHeight = null;
    });

    if (willOpen) {
      item.classList.add('is-open');
      head.setAttribute('aria-expanded', 'true');
      // 実際の高さを与えて滑らかに展開
      panel.style.maxHeight = panel.scrollHeight + 'px';
    }
  });
});

🤖 AIエージェント用プロンプト

このままコピーしてAIに貼り付け「追加する場所」だけ書き換えればOK
あなたは熟練のフロントエンドエンジニアです。私のWebサイトに「アコーディオン」の効果を追加してください。

# 追加してほしい効果
アコーディオン(UIコンポーネント)
見出しのクリックで本文を滑らかに開閉するアコーディオン。max-heightアニメとaria属性で、FAQやヘルプの折りたたみ表示に使えます。

# 追加する場所
👉【ここに対象箇所を記入:例「トップのヒーローセクション」「お問い合わせボタン」「記事カードの一覧」など】

# 参考実装(この見た目・挙動を再現してください)
【HTML】
<!-- アコーディオン:見出しをクリックすると本文が開閉する -->
<div class="acc">
  <h2 class="acc__title">よくある質問</h2>

  <div class="acc__item" data-acc>
    <button class="acc__head" aria-expanded="false">
      <span>配送にはどれくらいかかりますか?</span>
      <svg class="acc__icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M6 9l6 6 6-6"/></svg>
    </button>
    <div class="acc__panel"><p>ご注文確定後、通常2〜4営業日でお届けします。離島・一部地域は追加日数をいただく場合があります。</p></div>
  </div>

  <div class="acc__item" data-acc>
    <button class="acc__head" aria-expanded="false">
      <span>返品・交換はできますか?</span>
      <svg class="acc__icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M6 9l6 6 6-6"/></svg>
    </button>
    <div class="acc__panel"><p>到着後14日以内であれば未使用品に限り返品・交換を承ります。お問い合わせフォームからご連絡ください。</p></div>
  </div>

  <div class="acc__item" data-acc>
    <button class="acc__head" aria-expanded="false">
      <span>支払い方法を教えてください</span>
      <svg class="acc__icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M6 9l6 6 6-6"/></svg>
    </button>
    <div class="acc__panel"><p>各種クレジットカード、コンビニ決済、銀行振込、主要なQRコード決済に対応しています。</p></div>
  </div>
</div>

【CSS】
/* カラーとサイズをカスタムプロパティで管理 */
:root{
  --bg:#0f172a;
  --card:#1e293b;
  --line:#334155;
  --accent:#38bdf8;
  --text:#e2e8f0;
  --muted:#94a3b8;
  --radius:14px;
}
*{box-sizing:border-box}
body{
  margin:0;
  min-height:100vh;
  display:grid;
  place-items:center;
  padding:28px 16px;
  font-family:"Segoe UI",system-ui,sans-serif;
  color:var(--text);
  background:radial-gradient(1200px 500px at 50% -10%,#1e3a5f,transparent),var(--bg);
}
.acc{width:min(440px,100%)}
.acc__title{
  margin:0 0 16px;
  font-size:1.15rem;
  letter-spacing:.04em;
  display:flex;align-items:center;gap:10px;
}
.acc__title::before{
  content:"";width:8px;height:22px;border-radius:4px;
  background:linear-gradient(var(--accent),#6366f1);
}
.acc__item{
  background:var(--card);
  border:1px solid var(--line);
  border-radius:var(--radius);
  margin-bottom:10px;
  overflow:hidden;
  transition:border-color .25s;
}
.acc__item.is-open{border-color:var(--accent)}
.acc__head{
  width:100%;
  display:flex;align-items:center;justify-content:space-between;gap:12px;
  padding:16px 18px;
  background:none;border:none;cursor:pointer;
  color:inherit;font:inherit;font-weight:600;text-align:left;
}
.acc__head:focus-visible{outline:2px solid var(--accent);outline-offset:-2px}
.acc__icon{
  width:20px;height:20px;flex:none;
  fill:none;stroke:var(--accent);stroke-width:2.2;stroke-linecap:round;stroke-linejoin:round;
  transition:transform .3s ease;
}
.acc__item.is-open .acc__icon{transform:rotate(180deg)}
/* max-height アニメで滑らかに開閉 */
.acc__panel{
  max-height:0;overflow:hidden;
  transition:max-height .35s ease;
}
.acc__panel p{
  margin:0;padding:0 18px 18px;
  color:var(--muted);line-height:1.7;font-size:.92rem;
}
@media (prefers-reduced-motion:reduce){
  .acc__panel,.acc__icon{transition:none}
}

【JavaScript】
// 各アコーディオン項目に開閉トグルを設定
const items = document.querySelectorAll('[data-acc]');

items.forEach((item) => {
  const head = item.querySelector('.acc__head');
  const panel = item.querySelector('.acc__panel');
  if (!head || !panel) return; // null安全

  head.addEventListener('click', () => {
    const willOpen = !item.classList.contains('is-open');

    // 他の項目を閉じる(1つだけ開くモード)
    items.forEach((other) => {
      other.classList.remove('is-open');
      const oh = other.querySelector('.acc__head');
      const op = other.querySelector('.acc__panel');
      if (oh) oh.setAttribute('aria-expanded', 'false');
      if (op) op.style.maxHeight = null;
    });

    if (willOpen) {
      item.classList.add('is-open');
      head.setAttribute('aria-expanded', 'true');
      // 実際の高さを与えて滑らかに展開
      panel.style.maxHeight = panel.scrollHeight + 'px';
    }
  });
});

# 外部ライブラリ
なし(追加ライブラリ不要)

# 守ってほしいこと
- 既存のHTML構造・レイアウト・デザインを壊さないこと。必要に応じてクラス名・色・サイズを私のサイトに合わせて調整してよい。
- クラス名やidが既存と衝突しないよう、必要なら接頭辞で名前空間を分けること。
- レスポンシブ対応と prefers-reduced-motion への配慮を入れること。
- 私のサイトのフレームワーク(React / Vue / 素のHTML など)に合わせて実装すること。不明な場合は素のHTML/CSS/JSで提示し、組み込み手順も説明すること。
- 変更後の確認手順も簡潔に教えてください。