【初心者向け】モダンなアコーディオンメニュー4選!コピペで使えるコード付き

目次

はじめに

Web制作を独学で学び始めて、HTMLとCSSの基礎は理解できたものの、いざJavaScriptを使った実践的な実装となると、どこから手をつければいいのか迷ってしまうものです。

特に、多くのWebサイトで使用されているアコーディオンメニュー。

シンプルそうに見えて、実は適切な実装方法を知らないと、思わぬバグやスマートフォンでの表示崩れに悩まされることになります。
この記事では、モダンなアコーディオンメニューの実装方法を、5つのパターンで詳しく解説します。

全てのコードはコピー&ペーストですぐに使え、さらにカスタマイズのポイントも押さえているので、あなたのポートフォリオやクライアントワークですぐに活用できますので、ぜひ参考にしてみてください。

基本のアコーディオンは以下で解説しています。

1. シンプルなフェードインアコーディオン

まずは、最もベーシックでありながら、モダンな実装方法のアコーディオンメニューから見ていきましょう。

See the Pen Untitled by ryoma (@hwjgdjpk-the-decoder) on CodePen.

この実装の特徴は以下の通りです。

  • CSSアニメーションによるスムーズな動き
  • JavaScriptでの適切なアクセシビリティ対応
  • レスポンシブデザインへの対応
  • 軽量で高パフォーマンス

HTML

<div class="accordion">
  <button class="accordion-trigger" aria-expanded="false">
    <span>アコーディオンの見出し</span>
    <svg class="accordion-icon" width="24" height="24" viewBox="0 0 24 24">
      <path d="M6 9l6 6 6-6"></path>
    </svg>
  </button>
  <div class="accordion-content">
    <div class="accordion-body">
      アコーディオンの内容がここに表示されます。
    </div>
  </div>
</div>

CSS

.accordion {
  width: 100%;
  max-width: 600px;
  margin: 0 auto;
  border: 1px solid #e2e8f0;
  border-radius: 0.5rem;
  overflow: hidden;
}

.accordion-trigger {
  width: 100%;
  padding: 1rem;
  display: flex;
  justify-content: space-between;
  align-items: center;
  background: white;
  border: none;
  cursor: pointer;
  transition: background-color 0.2s;
}

.accordion-trigger:hover {
  background-color: #f8fafc;
}

.accordion-icon {
  transition: transform 0.2s;
}

.accordion-trigger[aria-expanded="true"] .accordion-icon {
  transform: rotate(180deg);
}

.accordion-content {
  max-height: 0;
  overflow: hidden;
  transition: max-height 0.2s ease-in-out;
}

.accordion-content.open {
  max-height: 200px;
}

.accordion-body {
  padding: 1rem;
}

JavaScript

document.querySelectorAll('.accordion-trigger').forEach(button => {
  button.addEventListener('click', () => {
    const content = button.nextElementSibling;
    const isExpanded = button.getAttribute('aria-expanded') === 'true';

    button.setAttribute('aria-expanded', !isExpanded);
    content.classList.toggle('open');
  });
});

コードの解説

  1. HTML構造
  • button要素を使用することで、キーボード操作に対応
  • aria-expanded属性でアクセシビリティを確保
  1. CSSの特徴
  • max-heightを使用したスムーズなアニメーション
  • transitionプロパティによる自然な動き
  • レスポンシブ対応のためのmax-width設定
  1. JavaScriptのポイント
  • 純粋なJavaScript(バニラJS)での実装
  • WAI-ARIAに準拠したアクセシビリティ対応
  • イベントデリゲーションによる効率的な処理

2.複数パネル対応のアコーディオングループ

よく見かけるFAQや質問回答形式で使われる、複数のパネルを制御する実用的なアコーディオンを実装です。

See the Pen Untitled by ryoma (@hwjgdjpk-the-decoder) on CodePen.

HTML

<div class="faq-accordion">
  <div class="panel">
    <button class="panel-header">
      Q1. アコーディオンメニューとは何ですか?
      <span class="icon">+</span>
    </button>
    <div class="panel-content">
      <div class="panel-body">
        <p>
          アコーディオンメニューは、クリックすると内容が展開・収納される
          UIコンポーネントです。限られたスペースで多くの情報を
          整理して表示できる便利な機能です。
        </p>
      </div>
    </div>
  </div>

  <div class="panel">
    <button class="panel-header">
      Q2. メリットを教えてください
      <span class="icon">+</span>
    </button>
    <div class="panel-content">
      <div class="panel-body">
        <p>
          主なメリットは以下の3つです:<br>
          ・画面スペースを効率的に使える<br>
          ・情報を整理して表示できる<br>
          ・ユーザーが必要な情報を選んで閲覧できる
        </p>
      </div>
    </div>
  </div>
</div>

CSS

.faq-accordion {
  max-width: 800px;
  margin: 0 auto;
}

.panel {
  border: 1px solid #ddd;
  border-radius: 8px;
  margin-bottom: 10px;
}

.panel-header {
  width: 100%;
  padding: 15px 20px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  background: #fff;
  border: none;
  cursor: pointer;
  font-size: 16px;
  text-align: left;
}

.panel-header:hover {
  background: #f8f9fa;
}

.icon {
  font-size: 20px;
  transition: transform 0.3s;
}

.panel-header.active .icon {
  transform: rotate(45deg);
}

.panel-content {
  height: 0;
  overflow: hidden;
  transition: height 0.3s ease-out;
}

.panel-body {
  padding: 20px;
  border-top: 1px solid #eee;
}

JavaScript

document.querySelectorAll('.panel-header').forEach(trigger => {
  trigger.addEventListener('click', function() {
    const content = this.nextElementSibling;
    const panel = this.closest('.panel');

    // 他のパネルを閉じる
    document.querySelectorAll('.panel-header').forEach(otherTrigger => {
      if (otherTrigger !== this) {
        otherTrigger.classList.remove('active');
        const otherContent = otherTrigger.nextElementSibling;
        otherContent.style.height = '0';
      }
    });

    // クリックしたパネルの処理
    this.classList.toggle('active');

    if (this.classList.contains('active')) {
      // 開く時は中身の高さを計算
      const body = content.querySelector('.panel-body');
      content.style.height = body.offsetHeight + 'px';
    } else {
      // 閉じる時は0
      content.style.height = '0';
    }
  });
});

実装のポイント

  1. 構造のポイント
  • 内容を包む要素を2重にして、パディングの変化を防ぐ
  • アイコンは疑似要素ではなくspanで実装し、回転を管理しやすく
  • ボタン要素を使用してキーボード操作に対応
  1. CSSのポイント
  • heightプロパティでアニメーション(max-heightより自然な動き)
  • パディングは固定値で持ち、高さだけを変化
  • 余計なアニメーションを入れすぎない
  1. JavaScriptのポイント
  • 実際の高さを計算して設定
  • パネルごとに適切な高さでアニメーション
  • 他のパネルを確実に閉じる制御

3.モダンなアニメーション付きアコーディオン

より洗練された見た目と動きを実現する、アニメーション重視のアコーディオンメニューを実装です。

See the Pen Untitled by ryoma (@hwjgdjpk-the-decoder) on CodePen.

HTML

<div class="modern-accordion">
  <div class="accordion-item">
    <button class="accordion-header">
      <div class="header-content">
        <span class="label">Section 1</span>
        <p class="title">基本的な使い方</p>
      </div>
      <div class="icon-wrap">
        <span class="line"></span>
        <span class="line"></span>
      </div>
    </button>
    <div class="accordion-body">
      <div class="body-content">
        <p>基本的な使い方について説明します。まずは基礎から始めて、徐々に応用的な使い方を学んでいきましょう。</p>
        <ul>
          <li>手順1: インストール方法</li>
          <li>手順2: 初期設定</li>
          <li>手順3: 基本操作</li>
        </ul>
      </div>
    </div>
  </div>

  <div class="accordion-item">
    <button class="accordion-header">
      <div class="header-content">
        <span class="label">Section 2</span>
        <p class="title">カスタマイズ</p>
      </div>
      <div class="icon-wrap">
        <span class="line"></span>
        <span class="line"></span>
      </div>
    </button>
    <div class="accordion-body">
      <div class="body-content">
        <p>見た目や動きをカスタマイズする方法を解説します。CSSとJavaScriptを使って様々なアレンジが可能です。</p>
      </div>
    </div>
  </div>
</div>

CSS

.modern-accordion {
  max-width: 800px;
  margin: 0 auto;
  --primary-color: #2563eb;
  --text-color: #1f2937;
  --border-color: #e5e7eb;
}

.accordion-item {
  border: 1px solid var(--border-color);
  border-radius: 12px;
  margin-bottom: 16px;
  overflow: hidden;
  background: white;
}

.accordion-header {
  width: 100%;
  padding: 24px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  background: none;
  border: none;
  cursor: pointer;
  transition: background-color 0.2s;
}

.accordion-header:hover {
  background-color: #f8fafc;
}

.header-content {
  text-align: left;
}

.label {
  font-size: 14px;
  color: var(--primary-color);
  margin-bottom: 4px;
  display: block;
}

.title {
  font-size: 18px;
  color: var(--text-color);
  margin: 0;
  font-weight: 500;
}

.icon-wrap {
  position: relative;
  width: 24px;
  height: 24px;
}

.line {
  position: absolute;
  top: 50%;
  left: 0;
  width: 100%;
  height: 2px;
  background-color: var(--text-color);
  transition: transform 0.3s ease;
}

.line:first-child {
  transform: translateY(-50%);
}

.line:last-child {
  transform: translateY(-50%) rotate(90deg);
}

.accordion-header.active .line:last-child {
  transform: translateY(-50%) rotate(0);
}

.accordion-body {
  height: 0;
  overflow: hidden;
  transition: height 0.3s ease-out;
}

.body-content {
  padding: 0 24px 24px;
}

/* アニメーション用の状態 */
.accordion-body.entering {
  height: var(--content-height);
}

.accordion-body.leaving {
  height: 0;
}

JavaScript

document.querySelectorAll('.accordion-header').forEach(header => {
  header.addEventListener('click', function() {
    const body = this.nextElementSibling;
    const content = body.querySelector('.body-content');
    const isExpanding = !this.classList.contains('active');

    // 他のアコーディオンを閉じる
    document.querySelectorAll('.accordion-header').forEach(otherHeader => {
      if (otherHeader !== this && otherHeader.classList.contains('active')) {
        otherHeader.classList.remove('active');
        const otherBody = otherHeader.nextElementSibling;
        closeAccordion(otherBody);
      }
    });

    // クリックされたアコーディオンの処理
    this.classList.toggle('active');

    if (isExpanding) {
      // 開く処理
      const height = content.offsetHeight;
      body.style.height = `${height}px`;
    } else {
      // 閉じる処理
      closeAccordion(body);
    }
  });
});

function closeAccordion(element) {
  // 高さを0にして閉じる
  element.style.height = '0';
}

// ウィンドウリサイズ時の高さ調整
window.addEventListener('resize', () => {
  document.querySelectorAll('.accordion-header.active').forEach(header => {
    const body = header.nextElementSibling;
    const content = body.querySelector('.body-content');
    body.style.height = `${content.offsetHeight}px`;
  });
});

ポイント解説

  1. デザインの工夫
  • CSS変数で色を管理し、カスタマイズを容易に
  • 十分な余白でコンテンツの可読性を向上
  • ホバー時の視覚的フィードバック
  1. アニメーションの特徴
  • プラス/マイナスアイコンの滑らかな変形
  • コンテンツの高さを正確に計算
  • リサイズ時の高さ自動調整
  1. 保守性の向上
  • アニメーションのタイミングを変数で管理
  • リサイズ対応で様々な画面サイズに対応

すみません、同じようなパターンでさらに見出し5を書いていきます。

4.タブ切り替え連動アコーディオン

次はタブメニューと連動して動作する、より実用的なアコーディオンメニューのパターンです。

See the Pen Untitled by ryoma (@hwjgdjpk-the-decoder) on CodePen.

この実装の特徴は以下の通りです。

  • タブ切り替えと連動した表示制御
  • スムーズな開閉アニメーション
  • コンテンツの高さに応じた自動調整

HTML

<div class="tab-accordion">
  <!-- タブメニュー -->
  <div class="tab-nav">
    <button class="tab-button active" data-target="tab1">タブ1</button>
    <button class="tab-button" data-target="tab2">タブ2</button>
    <button class="tab-button" data-target="tab3">タブ3</button>
  </div>

  <!-- アコーディオン -->
  <div class="tab-content active" id="tab1">
    <button class="tab-trigger" aria-expanded="false">
      <span>アコーディオン1</span>
      <svg class="tab-icon" width="24" height="24" viewBox="0 0 24 24">
        <path d="M6 9l6 6 6-6"></path>
      </svg>
    </button>
    <div class="accordion-content">
      <div class="accordion-body">
        タブ1のコンテンツがここに入ります。
        アコーディオンの開閉と連動して表示されます。
      </div>
    </div>
  </div>

  <div class="tab-content" id="tab2">
    <button class="tab-trigger" aria-expanded="false">
      <span>アコーディオン2</span>
      <svg class="tab-icon" width="24" height="24" viewBox="0 0 24 24">
        <path d="M6 9l6 6 6-6"></path>
      </svg>
    </button>
    <div class="accordion-content">
      <div class="accordion-body">
        タブ2のコンテンツです。
        別のタブに切り替えると自動的に閉じます。
      </div>
    </div>
  </div>

  <div class="tab-content" id="tab3">
    <button class="tab-trigger" aria-expanded="false">
      <span>アコーディオン3</span>
      <svg class="tab-icon" width="24" height="24" viewBox="0 0 24 24">
        <path d="M6 9l6 6 6-6"></path>
      </svg>
    </button>
    <div class="accordion-content">
      <div class="accordion-body">
        タブ3のコンテンツです。
        各タブで独立してアコーディオンが動作します。
      </div>
    </div>
  </div>
</div>

CSS

.tab-accordion {
  width: 100%;
  max-width: 800px;
  margin: 0 auto;
}

.tab-nav {
  display: flex;
  gap: 1rem;
  margin-bottom: 1rem;
}

.tab-button {
  padding: 0.75rem 1.5rem;
  background: #f1f5f9;
  border: none;
  border-radius: 0.5rem;
  cursor: pointer;
  transition: background-color 0.2s;
}

.tab-button:hover {
  background: #e2e8f0;
}

.tab-button.active {
  background: #3b82f6;
  color: white;
}

.tab-content {
  display: none;
  border: 1px solid #e2e8f0;
  border-radius: 0.5rem;
  overflow: hidden;
}

.tab-content.active {
  display: block;
}

.tab-trigger {
  width: 100%;
  padding: 1rem;
  display: flex;
  justify-content: space-between;
  align-items: center;
  background: white;
  border: none;
  cursor: pointer;
}

.tab-icon {
  transition: transform 0.2s;
}

.tab-trigger[aria-expanded="true"] .tab-icon {
  transform: rotate(180deg);
}

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

.accordion-content.open {
  max-height: 200px;
}

.accordion-body {
  padding: 1rem;
  border-top: 1px solid #e2e8f0;
}

JavaScript

// タブ切り替えの処理
document.querySelectorAll('.tab-button').forEach(button => {
  button.addEventListener('click', () => {
    // アクティブなタブを切り替え
    document.querySelectorAll('.tab-button').forEach(btn => {
      btn.classList.remove('active');
    });
    button.classList.add('active');

    // タブコンテンツを切り替え
    document.querySelectorAll('.tab-content').forEach(content => {
      content.classList.remove('active');
    });
    const targetId = button.dataset.target;
    document.getElementById(targetId).classList.add('active');

    // アコーディオンを全て閉じる
    document.querySelectorAll('.tab-trigger').forEach(trigger => {
      trigger.setAttribute('aria-expanded', 'false');
      trigger.nextElementSibling.classList.remove('open');
    });
  });
});

// アコーディオンの処理
document.querySelectorAll('.tab-trigger').forEach(trigger => {
  trigger.addEventListener('click', () => {
    const content = trigger.nextElementSibling;
    const isExpanded = trigger.getAttribute('aria-expanded') === 'true';

    trigger.setAttribute('aria-expanded', !isExpanded);
    content.classList.toggle('open');
  });
});

コードの解説

  • HTML構造
  • タブメニューとアコーディオンを組み合わせた階層構造
  • data-target属性でタブとコンテンツを紐付け
  • アクセシビリティを考慮した属性設定
  1. CSSの特徴
  • flexboxを使用したタブレイアウト
  • 状態に応じた表示/非表示の切り替え
  • スムーズな開閉アニメーション
  1. JavaScriptのポイント
  • タブ切り替え時のアコーディオンリセット
  • 独立した状態管理による安定した動作
  • 複数の要素を連動させた制御

さいごに

この記事では、実践的なアコーディオンメニューの実装について4つのパターンを解説しました。

それぞれのコードはコピー&ペーストですぐに使え、カスタマイズも容易な設計になっています。実際のプロジェクトでもすぐに活用できます。

また、アクセシビリティにも配慮した実装を心がけていますので、より多くのユーザーに使いやすいUIを提供できますのでぜひ参考にしてみてください。

関連記事

あわせて読みたい
【保存版】ShopifyFlow完全ガイド:設定方法から活用例まで徹底解説 ShopifyFlowの設定方法から活用例まで徹底解説 ECサイトの運営者のの多くが、日々の運営業務に追われて戦略的な施策を考える時間が取れないという悩みを抱えています。...
あわせて読みたい
【徹底解説】現役エンジニアが教えるCursor完全活用ガイド2024|AI時代の必須スキル 現役エンジニアが教えるCursor完全活用ガイド 毎日10時間以上コーディング作業をしている現役エンジニアとして、新しいコードエディタ「Cursor」の可能性に大きな期待を...
よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次