Accordion 1 — アコーディオン(FAQ・排他制御)

表示・インジケーター 初級

このコンポーネントについて

アコーディオン(accordion)は、クリックで内容を開閉できる折りたたみUIです。FAQ・設定画面・ヘルプページなど、情報量が多い画面をすっきり整理したいときに重宝します。

このページでは、複数のアイテムを同時に開けるFAQタイプ(Pattern 1)と、1つを開くと他が閉じる排他制御タイプ(Pattern 2)の2パターンを紹介します。どちらも同じHTML・CSS・JSで動作し、data-exclusive="true" 属性を付けるだけで挙動を切り替えられます。

  • FAQタイプ(Pattern 1) — 複数のアイテムを同時に展開できる。Q&Aページや設定項目の羅列に向く
  • 排他制御タイプ(Pattern 2) — 1つのアイテムを開くと他は自動的に閉じる。アコーディオン本来の挙動
  • スムーズな開閉アニメーションmax-height トランジションで自然な展開・収縮を実現
  • アクセシビリティ対応aria-expanded を切り替えてスクリーンリーダーに状態を伝える

実装のポイント・注意点

開閉アニメーションは max-height の CSS トランジションで実現しています。height: autoheight: 0 の間はアニメーションできませんが、max-height は数値間のトランジションができます。開くときは body.scrollHeight + 'px'max-height にセットし、閉じるときは必ず '0' を指定します。閉じるときに scrollHeight を使ってしまうとアニメーションが起きないため注意が必要です。

アクセシビリティのために aria-expanded 属性をボタンに付与しています。true が展開中、false が折りたたみ中を意味し、スクリーンリーダーが状態を読み上げられるようになります。CSSでも .ac-header[aria-expanded="true"] セレクターで展開時の見た目を制御しているため、JSとCSSを aria-expanded だけで一元管理できるのがポイントです。

排他制御は data-exclusive="true" 属性だけで切り替えられます。JSが root.dataset.exclusive を読んで排他モードかどうかを判断し、展開時に他のアイテムを閉じる処理を追加しているだけです。HTMLの属性1つで挙動を変えられるため、同じページに2種類を並べることも可能です。

HTML・CSS・バニラJavaScriptのみで実装しており、フレームワーク不要でコピペすぐに動きます。

デモ

Pattern 1 — FAQ(複数同時展開)

基本機能はすべて無料でご利用いただけます。プレミアムプランでは追加のストレージと優先サポートが含まれます。

閲覧はアカウントなしでも可能です。コンテンツを保存・投稿する場合はアカウント登録が必要です。

はい、iOS・Androidの主要ブラウザ(Chrome・Safari)に対応しています。専用アプリは現在開発中です。

はい、いつでも解約できます。解約後はプレミアム機能が使えなくなりますが、投稿データは保持されます。

Pattern 2 — 排他制御(1つだけ展開)

メール通知・プッシュ通知のオン/オフを切り替えられます。重要なお知らせのみ受け取る「まとめ通知」も選択できます。

プロフィールの公開範囲(全体公開・フォロワーのみ・非公開)を設定できます。ブロックリストの管理もこちらから行えます。

ライト/ダークテーマの切り替えと、フォントサイズ(小・中・大)の変更ができます。

パスワードの変更、2段階認証の設定、ログインセッションの管理を行えます。

サンプルソース

3つのファイルを同じフォルダに保存し、index.html をブラウザで開くとすぐに動作確認できます。
ファイル名:index.html / style.css / script.js — 保存時の文字コードは UTF-8 を指定してください(Shift-JISだと日本語が文字化けします)。

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>アコーディオン サンプル</title>
  <link rel="stylesheet" href="./style.css">
</head>
<body>

<!-- FAQ アコーディオン(複数同時展開)-->
<!-- 排他制御にしたい場合は data-exclusive="true" を追加してください -->
<div class="ac-root">
  <div class="ac-item">
    <button class="ac-header" aria-expanded="false">
      <span class="ac-title">このサービスは無料で使えますか?</span>
      <span class="ac-icon" aria-hidden="true"></span>
    </button>
    <div class="ac-body">
      <div class="ac-content">
        <p>基本機能はすべて無料でご利用いただけます。プレミアムプランでは追加のストレージと優先サポートが含まれます。</p>
      </div>
    </div>
  </div>
  <div class="ac-item">
    <button class="ac-header" aria-expanded="false">
      <span class="ac-title">アカウント登録は必要ですか?</span>
      <span class="ac-icon" aria-hidden="true"></span>
    </button>
    <div class="ac-body">
      <div class="ac-content">
        <p>閲覧はアカウントなしでも可能です。コンテンツを保存・投稿する場合はアカウント登録が必要です。</p>
      </div>
    </div>
  </div>
  <div class="ac-item">
    <button class="ac-header" aria-expanded="false">
      <span class="ac-title">スマートフォンからも使えますか?</span>
      <span class="ac-icon" aria-hidden="true"></span>
    </button>
    <div class="ac-body">
      <div class="ac-content">
        <p>はい、iOS・Androidの主要ブラウザ(Chrome・Safari)に対応しています。</p>
      </div>
    </div>
  </div>
</div>

<script src="./script.js"></script>
</body>
</html>
:root {
  --ac-primary: #2B7FE8;
  --ac-border:  #E5E9EF;
  --ac-radius:  8px;
}

*, *::before, *::after { box-sizing: border-box; }

body {
  font-family: sans-serif;
  padding: 24px;
  color: #1A2332;
}

/* アコーディオン全体のコンテナ */
.ac-root {
  border: 1.5px solid var(--ac-border);
  border-radius: var(--ac-radius);
  overflow: hidden;
  max-width: 520px;
  background: #fff;
}

/* 各アイテム */
.ac-item { border-bottom: 1px solid var(--ac-border); }
.ac-item:last-child { border-bottom: none; }

/* ヘッダーボタン */
.ac-header {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 16px 20px;
  background: none;
  border: none;
  cursor: pointer;
  font-family: sans-serif;
  font-size: 14px;
  font-weight: 600;
  color: #1A2332;
  text-align: left;
  transition: background 0.15s;
}

.ac-header:hover { background: #F8FAFC; }

/* 展開中のヘッダー */
.ac-header[aria-expanded="true"] {
  color: var(--ac-primary);
  background: #EFF6FF;
}

/* タイトルテキスト */
.ac-title { flex: 1; line-height: 1.5; }

/* アイコン(+/-) */
.ac-icon {
  flex-shrink: 0;
  width: 22px;
  height: 22px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: #E5E9EF;
  color: #5A6A7A;
  font-size: 15px;
  font-weight: 700;
  transition: background 0.15s, color 0.15s;
  user-select: none;
}

.ac-icon::before { content: '+'; }

.ac-header[aria-expanded="true"] .ac-icon {
  background: var(--ac-primary);
  color: #fff;
}

.ac-header[aria-expanded="true"] .ac-icon::before { content: '\2212'; }

/* 開閉エリア(max-height でアニメーション) */
.ac-body {
  max-height: 0;
  overflow: hidden;
  transition: max-height 0.3s ease;
}

/* コンテンツ */
.ac-content {
  padding: 4px 20px 20px;
  font-size: 14px;
  color: #5A6A7A;
  line-height: 1.7;
}

.ac-content p { margin: 0; }
// アコーディオンを初期化する
// data-exclusive="true" のとき排他制御モード(1つだけ展開)
document.querySelectorAll('.ac-root').forEach(function(root) {
  initAccordion(root);
});

function initAccordion(root) {
  var isExclusive = root.dataset.exclusive === 'true';

  root.querySelectorAll('.ac-header').forEach(function(btn) {
    btn.addEventListener('click', function() {
      var isOpen = btn.getAttribute('aria-expanded') === 'true';

      // 排他制御: 開こうとしているとき他のアイテムを閉じる
      if (isExclusive && !isOpen) {
        root.querySelectorAll('.ac-header').forEach(function(otherBtn) {
          if (otherBtn !== btn) {
            closeItem(otherBtn);
          }
        });
      }

      // 対象アイテムをトグル
      if (isOpen) {
        closeItem(btn);
      } else {
        openItem(btn);
      }
    });
  });
}

// アイテムを開く
function openItem(btn) {
  var body = btn.closest('.ac-item').querySelector('.ac-body');
  btn.setAttribute('aria-expanded', 'true');
  body.style.maxHeight = body.scrollHeight + 'px';
}

// アイテムを閉じる
function closeItem(btn) {
  var body = btn.closest('.ac-item').querySelector('.ac-body');
  btn.setAttribute('aria-expanded', 'false');
  body.style.maxHeight = '0';
}

AI用プロンプト

各パターンのプロンプトをコピーしてAIに渡すと、同様のコンポーネントを生成できます。

ChatGPTやClaudeにこのプロンプトを渡すと、同様のコンポーネントをゼロから生成・カスタマイズできます。項目数の変更や色のカスタマイズなど、要件を追記して使うのがおすすめです。

※ このプロンプトを使ってもデモとまったく同じ動作にならない場合があります。AIの解釈や生成タイミングによって差が出ることをご了承ください。

💡 jQuery・Vue・React など特定のライブラリで実装したい場合は、プロンプトの末尾に「〇〇を使って実装してください」と追記してください。

Pattern 1 — FAQ(複数同時展開)

# アコーディオン(FAQ タイプ)作成依頼

## 概要
クリックで回答を開閉できるFAQアコーディオンを実装してください。
複数のアイテムを同時に展開できるタイプです。

## 要件
- 質問ヘッダーをクリックすると回答が展開・折りたたみできる
- 複数のアイテムを同時に開いた状態にできる(排他制御なし)
- 開閉状態に合わせてアイコンを「+」→「−」に切り替える
- 開閉はスムーズなアニメーション(max-height トランジション)で行う
- aria-expanded を使ってアクセシビリティに対応する
- Q&A を4件用意してデモとして表示する

## 技術仕様
- HTML / CSS / バニラJavaScript で実装
- 外部ライブラリ:なし
- レスポンシブ対応:必要

## 動作詳細
ヘッダーボタンに aria-expanded="false" を初期値として付与する。
クリック時に aria-expanded を true/false に切り替え、
対応する .ac-body の max-height を 0 ↔ scrollHeight+'px' で切り替える。
CSS は .ac-header[aria-expanded="true"] セレクターで展開時の色・背景を制御する。
アイコンは CSS の ::before 擬似要素 + content プロパティで「+」↔「−」を切り替える。

## 出力形式
HTML・CSS・JavaScriptを分けて出力してください。
各ファイルは単独でコピー&ペーストして使えるよう記述してください。

Pattern 2 — 排他制御アコーディオン

# アコーディオン(排他制御タイプ)作成依頼

## 概要
1つのアイテムを開くと他が自動的に閉じる、排他制御付きアコーディオンを実装してください。
設定パネルやナビゲーションメニューに向いたタイプです。

## 要件
- アイテムヘッダーをクリックすると展開し、他の展開中アイテムは閉じる
- 展開中のヘッダーを再クリックすると閉じる
- 開閉状態に合わせてアイコンを「+」→「−」に切り替える
- 開閉はスムーズなアニメーション(max-height トランジション)で行う
- aria-expanded を使ってアクセシビリティに対応する
- 設定項目を4件(通知設定・プライバシー設定・表示設定・アカウント設定)用意してデモとして表示する

## 技術仕様
- HTML / CSS / バニラJavaScript で実装
- 外部ライブラリ:なし
- レスポンシブ対応:必要

## 動作詳細
ヘッダーボタンに aria-expanded="false" を初期値として付与する。
クリック時、展開しようとしている場合は他のすべてのヘッダーを閉じてから対象を開く。
閉じる処理:aria-expanded を false に、.ac-body の max-height を '0' にセット。
開く処理:aria-expanded を true に、.ac-body の max-height を body.scrollHeight+'px' にセット。
CSS は .ac-header[aria-expanded="true"] セレクターで展開時の色・背景を制御する。

## 出力形式
HTML・CSS・JavaScriptを分けて出力してください。
各ファイルは単独でコピー&ペーストして使えるよう記述してください。