本文へスキップ
  1. Web BENTO JS
  2. カテゴリ一覧
  3. ドロップダウン
04 / DROPDOWN 12 パーツ

JSドロップダウンメニュー実装 12選

コピペで動くドロップダウンメニューのサンプル集です。プレビューは実際にクリックやホバーで開閉できます。すべて外側クリック・Escキーで閉じる処理込みです。HTML / CSS / JS タブでコードを確認し、全部コピーを押せば1回の貼り付けで完成します。依存ライブラリなしのバニラJSで、data-bnto-* 属性による自動初期化式です。

該当するパーツが見つかりませんでした。

ベーシック

<div class="bnto-dd" data-bnto-dd>
  <button type="button" class="bnto-dd-btn">
    メニュー
    <svg class="ar" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6"/></svg>
  </button>
  <div class="bnto-dd-menu">
    <a href="#">項目1</a>
    <a href="#">項目2</a>
    <a href="#">項目3</a>
  </div>
</div>
使い方のコツ

外側クリックとEscキーで閉じる処理まで込みの基本形です。メニューを右揃えにしたいときは .bnto-dd-menuleft: 0right: 0 に変えるだけです。

ホバー展開(タッチはタップ)

<div class="bnto-dd2" data-bnto-dd-hover>
  <button type="button" class="bnto-dd2-btn">
    ホバーで開く
    <svg class="ar" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6"/></svg>
  </button>
  <div class="bnto-dd2-menu">
    <a href="#">項目1</a>
    <a href="#">項目2</a>
    <a href="#">項目3</a>
  </div>
</div>
使い方のコツ

ホバー式の弱点である「ボタンとメニューの隙間で閉じてしまう」問題は、透明の ::before ブリッジで解決済みです。タッチ端末では matchMedia('(hover: hover)') の判定で自動的にタップ開閉になります。

カスタムセレクト風

<!-- 選んだ値は hidden input に入るのでフォーム送信OK -->
<div class="bnto-sel" data-bnto-select>
  <button type="button" class="bnto-sel-btn">
    <span class="label">サイズを選択</span>
    <svg class="ar" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6"/></svg>
  </button>
  <div class="bnto-sel-menu">
    <button type="button" class="op" data-value="s">S サイズ</button>
    <button type="button" class="op" data-value="m">M サイズ</button>
    <button type="button" class="op" data-value="l">L サイズ</button>
  </div>
  <input type="hidden" name="size" value="">
</div>
使い方のコツ

フォーム内で使うときは hidden inputname を送信したいキー名に変更してください。単純な1択でデザインにこだわらないなら、ネイティブの <select> のほうがアクセシビリティは有利です(下のSEOコラム参照)。

ケバブメニュー(…)

<div class="bnto-kebab" data-bnto-kebab>
  <button type="button" class="bnto-kebab-btn" aria-label="その他の操作">
    <svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><circle cx="12" cy="5" r="2"/><circle cx="12" cy="12" r="2"/><circle cx="12" cy="19" r="2"/></svg>
  </button>
  <div class="bnto-kebab-menu">
    <button type="button">編集</button>
    <button type="button">複製</button>
    <button type="button" class="danger">削除</button>
  </div>
</div>
使い方のコツ

カード・テーブル行・コメント欄の右上に置く定番UIです。「削除」など取り返しのつかない操作は .danger で赤くして、確認ダイアログを挟むのがおすすめです。

ユーザーメニュー

<div class="bnto-user" data-bnto-usermenu>
  <button type="button" class="bnto-user-btn" aria-label="ユーザーメニュー">
    <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>
  </button>
  <div class="bnto-user-menu">
    <div class="who"><b>山田 太郎</b><span>[email protected]</span></div>
    <a href="#">プロフィール</a>
    <a href="#">設定</a>
    <div class="sep" role="separator"></div>
    <a href="#" class="out">ログアウト</a>
  </div>
</div>
使い方のコツ

アバターに画像を使うなら、ボタン内のsvgを <img>width:100%; height:100%; object-fit:cover; border-radius:50%)に差し替えてください。ヘッダーの右端に置く想定でメニューは右揃えです。

チェックボックス複数選択

<!-- メニュー内をクリックしても閉じないので複数チェックできる -->
<div class="bnto-ddc" data-bnto-ddcheck>
  <button type="button" class="bnto-ddc-btn">
    タグで絞り込み
    <span class="num"></span>
    <svg class="ar" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6"/></svg>
  </button>
  <div class="bnto-ddc-menu">
    <label><input type="checkbox" value="html">HTML</label>
    <label><input type="checkbox" value="css">CSS</label>
    <label><input type="checkbox" value="js">JavaScript</label>
  </div>
</div>
使い方のコツ

商品一覧の絞り込みフィルタに最適です。選択された値は dd.querySelectorAll('input:checked') で取れるので、change イベント内に絞り込み処理を足せばそのままフィルタUIになります。

2階層メニュー

<div class="bnto-dd7" data-bnto-dd-nest>
  <button type="button" class="bnto-dd7-btn">
    製品情報
    <svg class="ar" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6"/></svg>
  </button>
  <div class="bnto-dd7-menu">
    <a href="#">新着アイテム</a>
    <!-- サブメニュー付き項目 -->
    <div class="has-sub">
      <button type="button">カテゴリ
        <svg class="ar2" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="m9 18 6-6-6-6"/></svg>
      </button>
      <div class="sub">
        <a href="#">トップス</a>
        <a href="#">ボトムス</a>
      </div>
    </div>
    <a href="#">ランキング</a>
  </div>
</div>
使い方のコツ

サブメニューはPCではホバー、タッチ端末ではタップで開きます。画面右端に置くと見切れるので、その場合は .subleftright: calc(100% + 4px) に変えて左に開かせましょう。階層は2段までが目安です。それ以上はUIの再設計を検討してください。

該当なし

検索付きドロップダウン

<div class="bnto-dd8" data-bnto-dd-search>
  <button type="button" class="bnto-dd8-btn">
    <span class="label">都道府県を選択</span>
    <svg class="ar" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6"/></svg>
  </button>
  <div class="bnto-dd8-menu">
    <input type="text" class="srch" placeholder="入力して絞り込み…" aria-label="項目を検索">
    <div class="bnto-dd8-list">
      <button type="button" class="op">北海道</button>
      <button type="button" class="op">東京都</button>
      <button type="button" class="op">京都府</button>
      <!-- .op を必要なだけ -->
    </div>
    <p class="none">該当なし</p>
  </div>
</div>
使い方のコツ

都道府県・国名・タグなど選択肢が10を超えるときに効果的です。項目は .op ボタンを増やすだけでOKです。ひらがな検索に対応したいときは data-kana="とうきょうと" のような属性を足して判定に含めると親切です。

メガメニュー

<!-- パネルは nav の幅いっぱいに開く。列(.col)は増減OK -->
<nav class="bnto-mega" data-bnto-mega>
  <span class="brand">LOGO</span>
  <button type="button" class="bnto-mega-btn">
    製品
    <svg class="ar" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6"/></svg>
  </button>
  <div class="bnto-mega-panel">
    <div class="col">
      <p class="ttl">見出し1</p>
      <a href="#">項目1</a>
      <a href="#">項目2</a>
    </div>
    <div class="col">
      <p class="ttl">見出し2</p>
      <a href="#">項目3</a>
      <a href="#">項目4</a>
    </div>
  </div>
</nav>
使い方のコツ

項目数の多いコーポレートサイトやECのグローバルナビの定番です。デモは縮小表示ですが、実サイトではヘッダーの nav の幅いっぱいにパネルが開きます。列数は grid-template-columns: repeat(3, 1fr) のように変更でき、リンクが多いときは見出し(.ttl)でグループ分けすると迷いません。

この枠内で右クリック

右クリックコンテキストメニュー

<!-- .bnto-ctx-area 内の右クリックで独自メニューが開く -->
<div class="bnto-ctx" data-bnto-contextmenu>
  <div class="bnto-ctx-area">この枠内で右クリック</div>
  <div class="bnto-ctx-menu">
    <button type="button">コピー</button>
    <button type="button">名前を変更</button>
    <button type="button" class="danger">削除</button>
  </div>
</div>
使い方のコツ

ファイル一覧やカード一覧など「編集系のUI」に向いたパーツです。メニューは position: fixed なのでスクロール中でもカーソル位置に正確に出ます。スマホには右クリックがないため、同じ操作へたどり着けるボタン(ケバブメニューなど)を併設しておくと親切です。

通知
新しいコメントが届きました5分前
資料が共有されました1時間前
メンテナンスは終了しました昨日

通知ベル

<!-- 未読は .item に .is-new を付ける。バッジの数字はJSが自動計算 -->
<div class="bnto-bell" data-bnto-notifybell>
  <button type="button" class="bnto-bell-btn" aria-label="通知を開く">
    <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9"/><path d="M10.3 21a1.94 1.94 0 0 0 3.4 0"/></svg>
    <span class="badge"></span>
  </button>
  <div class="bnto-bell-menu">
    <div class="head"><b>通知</b><button type="button" class="readall">すべて既読にする</button></div>
    <div class="item is-new"><b>新しいコメントが届きました</b><span>5分前</span></div>
    <div class="item is-new"><b>資料が共有されました</b><span>1時間前</span></div>
    <div class="item"><b>メンテナンスは終了しました</b><span>昨日</span></div>
  </div>
</div>
使い方のコツ

バッジの数字は .item.is-new の数から自動計算されるので、HTML側で未読クラスを付け外しするだけで反映されます。サーバーから通知を取得する場合は、.item 要素を生成してメニューに追加し、「すべて既読にする」のクリックで既読APIを呼ぶ処理を readall のリスナーに足してください。

複数選択チップ

<!-- 選んだ項目がチップになって並ぶ。×で個別解除 -->
<div class="bnto-chip" data-bnto-chipselect>
  <div class="bnto-chip-row" aria-live="polite"></div>
  <button type="button" class="bnto-chip-btn">
    スキルを選択
    <svg class="ar" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6"/></svg>
  </button>
  <div class="bnto-chip-menu">
    <button type="button" class="op">HTML</button>
    <button type="button" class="op">CSS</button>
    <button type="button" class="op">JavaScript</button>
    <button type="button" class="op">デザイン</button>
  </div>
</div>
使い方のコツ

タグ付けやスキル選択など「複数選ぶ」入力の定番UIです。メニュー内をクリックしても閉じないので連続で選べます。フォーム送信に載せたいときは、チップ生成時に <input type="hidden" name="tags[]"> を一緒に追加し、×での解除時に削除する処理を足してください。

JSドロップダウン実装の基礎知識

ドロップダウンは、ボタンをクリック(またはホバー)するとメニューが下に開く定番UIです。ナビゲーション・操作メニュー・フォームの選択肢など使いどころが幅広い一方、「開く」より「閉じる」処理のほうが難しいのがドロップダウンの特徴です。このページのサンプルは外側クリック・Escキーで閉じる処理まで実装済み(ホバー展開型はタッチ時に同じ挙動へフォールバック)の、依存ライブラリなしのバニラJavaScriptです。

使い方は、HTML / CSS / JSの3タブをそれぞれ貼り付けるか、「全部コピー」ボタンでひとまとめになったコードを1回貼り付けるだけです。初期化は data-bnto-* 属性の自動検出式なので、同じページに何個置いても、うっかりコードを2回貼っても壊れません。

「外側クリックで閉じる」が使い心地を決める

メニューを開いたあと、ユーザーは「どこか別の場所をクリックすれば閉じる」と無意識に期待します。実装のコツは、documentにclickリスナーを1回だけ登録し、クリック位置がドロップダウンの内側かどうかを e.target.closest() で判定することです。個々のボタンにリスナーを増やしていく方式だと、設置数が増えるほどリスナーが乱立して競合の原因になります。あわせてEscキーで閉じる処理も入れておくと、キーボードユーザーにも快適です。

タッチデバイスへの配慮

スマホやタブレットには「ホバー」がありません。ホバー展開式のメニューをそのまま出すと、タッチユーザーは開けない・閉じられないUIになってしまいます。このページのホバー展開型は matchMedia('(hover: hover)') でホバー可能な環境かを判定し、タッチ環境では自動的にタップ開閉へフォールバックします。またホバー式は、ボタンとメニューの数ピクセルの隙間でメニューが閉じてしまう「ホバー切れ」が起きがちなので、透明の疑似要素ブリッジで隙間を埋めています。

ネイティブselectとの使い分け

「フォームで1つ選ぶだけ」なら、まずネイティブの <select> を検討してください。キーボード操作・スクリーンリーダー対応・スマホでの専用ピッカー表示など、ブラウザ標準の恩恵が非常に大きいためです。JS製のカスタムドロップダウンが活きるのは、デザインの統一・アイコン付きの項目・検索による絞り込み・チェックボックスでの複数選択など、selectでは表現できない要件があるときです。その場合も、選んだ値は hidden input に入れておけば通常のフォーム送信にそのまま乗せられます。

「全部コピー」と「分けて貼る」の使い分け

「全部コピー」を使うと、HTML・CSS・JavaScriptがひとつの自己完結ブロックとしてコピーされるので、1回の貼り付けだけですぐ動きを確認できます。動作のお試しや、1ページ完結のLPに組み込むときはこの方法が手軽です。

一方、本番サイトへ本格的に組み込む場合は、CSSはスタイルシート(.cssファイル)へ、JavaScriptは</body>直前や共通の.jsファイルへ、HTMLは使いたい場所へと分けて貼るのが一般的です。スタイルとスクリプトを1か所に集約できるため、複数ページ・複数パーツで使い回すときの管理がしやすく、ブラウザのキャッシュも効きやすくなります。なお、同じパーツをページ内で何度も使う場合でも、CSSとJSを貼るのは1回だけで大丈夫です(初期化スクリプトが該当要素すべてに自動で効きます)。

よくある質問

コピーしたドロップダウンはそのまま動きますか?
はい。「全部コピー」を押すとHTML・CSS・JSがひとつの自己完結ブロックになってコピーされ、ページに貼り付けるだけで動きます。ライブラリの読み込みは一切不要です。
外側クリックで閉じる処理はどう実装していますか?
document への click リスナーを1回だけ登録し、クリックされた場所がドロップダウンの内側かどうかを closest() で判定しています。複数設置してもコードを2回貼っても、リスナーが増えて競合することはありません。Escキーでも閉じられます。
スマホなどのタッチデバイスでも使えますか?
使えます。ホバー展開タイプは matchMedia('(hover: hover)') でホバーできない環境を判定し、自動的にタップ開閉へ切り替わります。その他のタイプはすべてクリック(タップ)式なので、そのまま動きます。
ネイティブのselect要素とはどう使い分ければいいですか?
フォームで1つ選ぶだけならネイティブの select 要素が最適です。キーボード操作やスクリーンリーダー対応がブラウザ標準で提供されるためです。デザインの統一・検索付き・複数選択など select では難しい表現が必要なときにJS製を使い、値は hidden input でフォームに渡します。
このページのパーツはAIコーディングツールでも使えますか?
はい。Claude CodeやCursorのようにWebページを読めるAIには、このページのURLとパーツ名(例:「ホバー展開(タッチはタップ)」)を伝えるだけで実装を指示できます。AIがWebを読めない場合は、「全部コピー」で取得した自己完結ブロックをチャットに貼り付けて調整を頼むのが確実です。改変後のコードは、公開前に動作確認することをおすすめします。

関連カテゴリ

ドロップダウンと組み合わせて使いやすいパーツはこちらです。