【JavaScript】ドロップダウンメニューを作る方法

ボタンをクリックするとメニューが開く「ドロップダウンメニュー」を、HTML・CSS・JavaScriptで作ります。

基本はボタンのクリックでメニューの表示を切り替えるだけですが、メニュー外のクリックや Escape キーで閉じるaria-expanded で状態を伝える、といった配慮を入れると使いやすくなります。実際に動くデモで確認しましょう。三本線で開くタイプはハンバーガーメニューの作り方も参考になります。

この記事の結論:ボタンのクリックで classList.toggle("open") を呼んでメニューを開閉します。aria-expanded の更新メニュー外クリック・Escapeで閉じる処理を加えるとアクセシブルです。display は直接アニメーションできないため、フェードさせたいときはmax-heightopacity を使います。
スポンサーリンク

まず検討:用途次第でネイティブが最適

「ドロップダウン」と言っても用途はさまざまで、JavaScriptを書かずにネイティブ要素で済む場合があります。まずは次を検討しましょう。

  • フォームの選択肢(1つ選ぶ)なら <select> 要素。スマホでもOS標準のピッカーが出て使いやすい
  • クリックで開閉する開示(アコーディオン的)なら <details> / <summary>。JSなしで開閉でき、キーボード操作も標準対応
  • 見た目を自由にしたいナビ用ドロップダウンや、ホバー/クリックの挙動を細かく制御したいときは、以下のJavaScript実装

この記事では3つ目のカスタム実装を、アクセシビリティに配慮して作ります。

完成イメージ(動くデモ)

↓ ボタンでメニューが開閉します(外クリック・Escapeでも閉じます):

HTMLの構造

ボタンと、その下に表示するメニューを用意します。ボタンには aria-expanded / aria-controls を付けて状態を示します。

HTML
<div class="dropdown">
  <button class="dropbtn" aria-expanded="false" aria-controls="menu">メニュー</button>
  <div id="menu" class="dropdown-content">
    <a href="#">リンク 1</a>
    <a href="#">リンク 2</a>
    <a href="#">リンク 3</a>
  </div>
</div>

CSSでスタイリング

メニューは初期状態で display: none.open が付いたら表示します。position: absolute でボタンの下に重ねて表示します。

CSS
.dropdown {
  position: relative;
  display: inline-block;
}
.dropbtn {
  background: #3498db; color: #fff; padding: 10px 14px;
  border: none; cursor: pointer;
}
.dropdown-content {
  display: none;
  position: absolute; top: 100%; left: 0;
  min-width: 160px; background: #fff;
  box-shadow: 0 8px 16px rgba(0,0,0,0.2);
  z-index: 1;
}
.dropdown-content.open { display: block; } /* 開いたとき表示 */
.dropdown-content a { display: block; padding: 12px 16px; color: #333; text-decoration: none; }
.dropdown-content a:hover { background: #f1f1f1; }

JavaScriptで開閉する

ボタンのクリックで classList.toggle("open") を呼びます。あわせて aria-expanded を更新します。直接 style.display をいじるより、クラスで切り替える方が見通しが良くなります。

JavaScript:開閉の基本
const btn = document.querySelector(".dropbtn");
const menu = document.getElementById("menu");

btn.addEventListener("click", (event) => {
  event.stopPropagation(); // 「外クリック」と誤判定されないように
  const isOpen = menu.classList.toggle("open");
  btn.setAttribute("aria-expanded", String(isOpen));
});

クラス操作はclassListの使い方、表示/非表示の切り替えは要素の表示/非表示を切り替える方法も参考になります。

メニュー外クリック・Escapeで閉じる

メニューを開いたあと、外側をクリックしたときやEscapeキーを押したときに閉じると親切です。

JavaScript:外クリック・Escapeで閉じる
function closeMenu() {
  menu.classList.remove("open");
  btn.setAttribute("aria-expanded", "false");
}

// メニューの外をクリックしたら閉じる
document.addEventListener("click", (event) => {
  if (!event.target.closest(".dropdown")) {
    closeMenu();
  }
});

// Escapeキーで閉じる
document.addEventListener("keydown", (event) => {
  if (event.key === "Escape") closeMenu();
});

キーボード操作・フォーカス管理に対応する

マウスだけでなくキーボードでも快適に操作できると、アクセシビリティが大きく向上します。↓キーで開いて項目へ移動・↑↓で項目間を移動・Escapeで閉じてボタンに戻る、を実装します。

JavaScript:矢印キー・フォーカス移動
const items = menu.querySelectorAll("a"); // メニュー内のリンク

function openMenu() {
  menu.classList.add("open");
  btn.setAttribute("aria-expanded", "true");
  items[0]?.focus();             // 開いたら最初の項目へ
}
function closeMenu(returnFocus) {
  menu.classList.remove("open");
  btn.setAttribute("aria-expanded", "false");
  if (returnFocus) btn.focus();  // ボタンへフォーカスを戻す
}

// ボタン上で ↓ を押したら開いて項目へ
btn.addEventListener("keydown", (e) => {
  if (e.key === "ArrowDown") { e.preventDefault(); openMenu(); }
});

// メニュー内で ↑↓ 移動・Escapeで閉じる
menu.addEventListener("keydown", (e) => {
  const list = [...items];
  const i = list.indexOf(document.activeElement);
  if (e.key === "ArrowDown") { e.preventDefault(); list[Math.min(i + 1, list.length - 1)]?.focus(); }
  else if (e.key === "ArrowUp") { e.preventDefault(); list[Math.max(i - 1, 0)]?.focus(); }
  else if (e.key === "Escape") { closeMenu(true); }
});
WAI-ARIAの考え方:本格的なメニューでは、メニュー要素に role="menu"、各項目に role="menuitem" を付け、矢印キーでの移動を提供します。まずは「↓で開く・矢印で移動・Escapeで閉じてボタンに戻る」を押さえるだけでも、キーボードユーザーの使い勝手が大きく変わります。

開閉にアニメーションを付ける

displayは直接アニメできない:display: none ⇔ block の切り替えはアニメーションできません。ふわっと開きたいときは、max-height0→指定値)やopacitytransition を使います。クラス(.open)で max-height を切り替えるのが定番です。
CSS:max-heightでスライド表示
.dropdown-content {
  max-height: 0;
  overflow: hidden;
  transition: max-height 0.3s ease;
}
.dropdown-content.open {
  max-height: 300px; /* 中身が収まる十分な値 */
}

よくある質問(FAQ)

QJavaScriptでドロップダウンメニューを実装するには?
Aボタンの click でメニュー要素の classList.toggle("open") を呼んで開閉します。CSSの positionz-index でボタンの下に重ねて表示し、.open 時に display: block にします。
Qモバイルでもタップで開くようにするには?
Aホバーが使えないモバイルでは click イベントでトグルする実装にします。PCでホバー、モバイルでタップと分けたい場合は @media (hover: hover) を使う方法もあります。
Qメニュー外をクリックしたら閉じるには?
Adocumentclick を監視し、クリックされた要素がドロップダウン内(event.target.closest(".dropdown"))でなければ閉じます。ボタン側では event.stopPropagation() で誤作動を防ぎます。
QドロップダウンのアニメーションをCSSで付けるには?
Adisplay はアニメーションできないため、max-height: 0 から指定値へのtransition が一般的です。opacitytransform と組み合わせる方法もあります。
Qキーボードだけで操作できるようにするには?
Aボタンで ArrowDown を押したら開いて最初の項目へフォーカスし、メニュー内では ArrowUp / ArrowDown で項目間を移動、Escape で閉じてボタンへフォーカスを戻します。フォーム選択なら <select> を使えば標準でキーボード対応です。

まとめ

ドロップダウンメニューは、ボタンのクリックで classList.toggle("open") して開閉するのが基本です。CSSで .open 時の表示と位置(position: absolute)を定義します。

使いやすくするには、aria-expanded の更新と、メニュー外クリック・Escapeで閉じる処理を加えましょう。ふわっと開くアニメーションは display ではなく max-heightopacity で実現します。