ASP.NET Web FormsでBootstrapのドロップダウン(サブメニュー)とモーダルが競合する原因と解決策

ASP.NET Web Forms(C#)でGridViewのボタンからBootstrapのモーダルを表示しようとして、Bootstrapを参照した瞬間にドロップダウン(サブメニュー)の位置ずれやフォント崩れが起きるケースは珍しくありません。原因の多くは「BootstrapのCSS/JSの世代ズレ」「二重読み込み」「既存CSS/JSとの競合」です。現場で最短で直すための確認ポイントと実装のコツを、手順ベースで整理します。

目次

症状を先に分解すると原因が見えやすい

「モーダルは出る(ように見える)が、同じページのドロップダウン(サブメニュー)が崩れる」「Bootstrap参照を外すとドロップダウンは戻るが、モーダルは出ない」という状況は、見た目だけでは原因を誤判定しやすい代表例です。まずは“何が動いていて、何が動いていないか”を分けて考えます。

症状よくある原因最初に見る場所
CSSを入れたらドロップダウンの位置・フォント・余白が崩れるBootstrapのCSSが別テーマのCSSを上書き/Bootstrapの世代が違うCSSを混ぜている読み込まれているCSSの本数・順番(Site.Master、共通レイアウト、CDN追加箇所)
モーダルが出るが、閉じる・背景暗転・ボタン反応が不安定BootstrapのJSが別世代/jQueryやPopperなど依存関係が揃っていない/二重読み込みNetworkタブのbootstrap.js、jquery.js、bundleの有無
ドロップダウン自体が開かない/クリックで何も起きないdata属性が世代違い(data-toggle と data-bs-toggle)/JS未読込HTML属性、Consoleエラー
サブメニュー(多段)が特に崩れる・はみ出る・クリックしづらいそもそも標準で多段を想定していない/親要素のoverflowやz-indexが原因CSS(overflow, position, z-index)とメニュー構造

結論:Bootstrapは「1世代・1セット」に統一するのが最短ルート

この手の不具合の原因は、ほぼ次のどれかに集約されます。

  • BootstrapのCSSとJSの世代が一致していない(例:Bootstrap 3のHTMLにBootstrap 5のJSを当てている、またはその逆)
  • Bootstrap(またはjQuery)が二重読み込み(テンプレートに含まれているのにCDNを追加した、など)
  • 既存のCSS/JS(社内共通CSS、テーマ、jQuery UIなど)が競合(.dropdown-menu や .modal の上書き)

特にWeb Formsでは、Site.Masterや共通ユーザーコントロール、別のaspxで読み込んでいる共通スクリプトが積み重なりやすく、本人が意識していない場所でBootstrapやjQueryが既に読み込まれているケースが頻発します。

Bootstrap 3/4/5で「前提」が変わるポイント

Bootstrapは同じ“Bootstrap”でも世代で前提が大きく異なります。見た目(CSS)だけ入れ替えると崩れ、JSだけ入れ替えると動かない、という現象が起きやすいのはこのためです。

観点Bootstrap 3系Bootstrap 4系Bootstrap 5系
jQuery依存必須必須不要
Popper依存不要必須(Dropdown等)必須(通常bundleに同梱)
data属性の代表例data-toggle=”dropdown” / data-toggle=”modal”data-toggle=”dropdown” / data-toggle=”modal”data-bs-toggle=”dropdown” / data-bs-toggle=”modal”
「$(…).modal()」のようなjQuery API利用する利用する利用しない(bootstrap.Modalを使う)
多段ドロップダウン標準で想定薄い(工夫が必要)同上同上(特に標準は1段想定)

つまり、HTML(data属性)・CSS・JS・依存関係はセットで揃える必要があります。「CSSだけ追加」「JSだけ追加」は一時的に“動いたように見えても”どこかで破綻します。

二重読み込みを潰す:Web Formsでの定番落とし穴

Web Formsプロジェクトでは、次のどこかにBootstrapやjQueryが潜みがちです。

潜みやすい場所ありがちな状況対策
Site.Master / MasterPageテンプレートがBootstrap + jQueryを既に読み込んでいるMaster側を基準に「1セット」だけ残す(ページ側のCDN追加を消す)
各.aspxのContentページごとにCDNを足してしまい、別バージョンが混ざる共通化(MasterまたはBundle)し、ページ単位の追加を禁止する
ユーザーコントロール(.ascx)部品側で勝手にCSS/JSを読み込んでいる部品からライブラリ読み込みを撤去し、レイアウト層に集約する
BundleConfig / ScriptManagerバンドルで読み込みつつ、手動で同じものを入れているバンドル一本に寄せ、直接参照を消す

実務で確実なのは、ブラウザで「ページのソース」(もしくはDevToolsのNetwork)を見て、次を機械的に確認する方法です。

  • bootstrap.css(またはbootstrap.min.css)が何本読み込まれているか
  • bootstrap.js(またはbootstrap.bundle.js)が何本読み込まれているか
  • jquery.jsが何本読み込まれているか(Bootstrap 3/4を使う場合は特に重要)
  • 読み込み順(後勝ちで上書きされるため、順番も結果に直結します)

読み込み順の基本:これを守るだけで直るケースが多い

Bootstrapは“読み込み順”が崩れた瞬間に、ドロップダウンやモーダルが沈黙したり、イベントが不安定になります。まずは以下の基本形に揃えます(どちらか一方だけを採用してください)。

Bootstrap 3系の基本形(jQuery必須)

<!-- CSSはhead側でOK(順序は「自前のCSSはBootstrapの後」にすると調整しやすい) -->
<link rel="stylesheet" href="/Content/bootstrap.min.css">
<link rel="stylesheet" href="/Content/site.css">



Bootstrap 5系の基本形(jQuery不要、bundle推奨)

<link rel="stylesheet" href="/Content/bootstrap.min.css">
<link rel="stylesheet" href="/Content/site.css">


「モーダルのためにbootstrap.min.cssを足したらモーダルが出た」という場合、裏で既にbootstrap.jsが読み込まれている可能性が高いです。そしてそのbootstrap.jsが別世代(例:古いBootstrap 2/3)で、そこに別世代のCSSを足した結果、ドロップダウンの見た目が崩れる…という流れがよく起きます。

Consoleのエラーは“世代ズレ”を教えてくれる

DevTools(F12)のConsoleに出るメッセージは、原因の当たりを付けるのに役立ちます。代表的なサインをまとめます。

Consoleに出やすいサイン意味打ち手
「$(…).modal is not a function」BootstrapのjQueryプラグインが存在しない(Bootstrap 5を読み込んでいる、またはbootstrap.js未読込)Bootstrap 3/4ならjQuery→bootstrap.jsの順で読み込む。Bootstrap 5ならbootstrap.Modalを使う
「jQuery is not defined」Bootstrap 3/4を使っているのにjQueryが先に読み込まれていない順序修正(jQueryを先に)
クリックしてもdropdownが開かないがエラーはないdata属性が世代違い、または別JSに上書きされている可能性data-toggle / data-bs-toggleの整合、二重読み込みの除去

サブメニュー(多段ドロップダウン)が崩れるのは“標準外”のことが多い

ここが重要なポイントです。Bootstrapのドロップダウンは、基本的に1段(単層)メニューを前提にしていることが多く、多段(サブメニュー)は標準で手厚く面倒を見てくれない場合があります。そのため、Bootstrapを導入したことで“崩れが顕在化”することがあります。

多段にしたい場合の現実的な選択肢は次の3つです。

  • 多段対応のCSS/JS(プラグイン)を使う:導入工数が低いが、Bootstrapの世代に合うものを選ぶ必要がある
  • Bootstrap標準の構造に寄せてCSSで調整する:小規模なら十分だが、アクセシビリティやタッチ操作で罠が出やすい
  • UIを再設計する:メガメニュー、サイドナビ、アコーディオンなど、運用と保守が安定しやすい

さらに、サブメニュー崩れには“競合”以外にレイアウト都合も混ざります。例えば、メニューの親コンテナに overflow: hidden が指定されていると、サブメニューが親要素からはみ出した瞬間に切り取られて「出ていないように見える」ことがあります。Web FormsではPanelやテンプレート内のdivにoverflow指定が入っているケースがあるため要注意です。

多段メニューで特に確認したいCSS

  • overflow: hidden / overflow: auto が親要素に付いていないか(付いているとドロップダウンが“切れる”)
  • position が意図せず変わっていないか(relative/absolute/fixedの影響)
  • z-index の競合(モーダルとドロップダウンが重なると表示順が破綻しやすい)

モーダルとドロップダウンが同居するときの「z-index」と「配置」の落とし穴

モーダルとドロップダウンはどちらも重なり要素を多用します。だからこそ、CSSの小さな変更が大きな破綻に繋がります。特に次が多いです。

現象原因の例対処の方向性
ドロップダウンがモーダルの背面に隠れるz-indexの階層が逆転している設計上は「モーダル表示中はメニューを操作させない」方が自然。必要ならz-index調整だが乱用しない
ドロップダウンが途中で切れて見える親要素にoverflowが指定されているメニュー周辺のコンテナだけoverflowを解除、またはメニューを切れない位置に移動
クリック位置がずれる/サブメニューにカーソルが届かないpadding/line-heightがBootstrapにより変化、hover領域が崩れる多段メニュー用のCSSを見直す(Bootstrap標準の値に寄せる)
ドロップダウンのフォントやアイコンだけ変わったリセットCSSやテーマCSSがBootstrapに上書きされた読み込み順の調整(Bootstrap→テーマ→上書きCSS)と、セレクタの競合解消

“直すためにz-indexを上げ続ける”のは長期的に破綻しがちです。まずは二重読み込みと世代統一を終えてから、必要最低限だけ調整するのが安全です。

ASP.NET Web Forms(GridView)でモーダルを出すときの実装パターン

Web Forms固有でつまずくのが、GridView内ボタンのクリックがサーバーポストバックになる点です。ポストバックが走るとDOMが再構築されるため、クライアント側の状態(「モーダルが開いている」など)が消えます。要件に合わせて次のどちらかを選びます。

パターンA:ポストバックさせずにクライアント側でモーダルを開く

「単に詳細を表示したい」「サーバー処理は不要」という場合はこれが最も安定します。GridView内のボタンをLinkButtonにして、OnClientClickでモーダルを開き、return falseでポストバックを止めます。

Bootstrap 3系の例

<asp:LinkButton runat="server"
  Text="詳細"
  OnClientClick="$('#detailModal').modal('show'); return false;" />

Bootstrap 5系の例

<asp:LinkButton runat="server"
  Text="詳細"
  OnClientClick="
    var m = new bootstrap.Modal(document.getElementById('detailModal'));
    m.show();
    return false;" />

この方法で重要なのは、モーダルに表示するデータをどう渡すかです。行の値をdata属性に埋める、hiddenに入れる、JavaScriptで取得して表示するなど、用途に合わせて設計します。

パターンB:サーバー処理後にモーダルを開く(RowCommandなど)

「クリックした行のIDでサーバーから詳細を取ってきて表示したい」「権限チェックをしたい」など、サーバー処理が必要なら、ポストバック後にモーダルを再表示します。ここでポイントになるのが、“サーバー側でモーダル表示用のスクリプトを登録する”ことです。

Bootstrap 3系:ScriptManager.RegisterStartupScriptの例

// GridViewのRowCommandなどで
ScriptManager.RegisterStartupScript(
  this,
  this.GetType(),
  "OpenModal",
  "$('#detailModal').modal('show');",
  true
);

Bootstrap 5系:bootstrap.Modalで表示する例

var script =
"var m = new bootstrap.Modal(document.getElementById('detailModal')); m.show();";
ScriptManager.RegisterStartupScript(this, this.GetType(), "OpenModal", script, true);

Web Formsで地味に効くのがClientIDの問題です。サーバーコントロールは描画時にIDが変わることがあるため、JavaScript側で要素を掴めずに失敗します。対策としては、次のどれかを採用します。

  • ClientIDMode=”Static” を使ってIDを固定する(適用範囲に注意)
  • スクリプト内で <%= detailModal.ClientID %> のようにサーバーから実IDを埋め込む
  • idではなくclassやdata属性で要素を特定する

UpdatePanelを使っている場合の追加注意点

UpdatePanelで部分更新していると、ポストバックが“非同期”になり、イベントの再バインドやDOMの差し替えが絡んで不具合が増えます。モーダル表示自体はRegisterStartupScriptで出せることが多いですが、ドロップダウンやサブメニュー系の自前スクリプトは「初回だけ動いて、その後動かない」ことがあります。

その場合は、非同期更新後に初期化処理を走らせます(概念例)。

// Microsoft Ajax(UpdatePanel)を使う場合の概念例
var prm = Sys.WebForms.PageRequestManager.getInstance();
prm.add_endRequest(function () {
  // ここでドロップダウン/サブメニューの再初期化を行う
});

ただし、ここまで行くなら、そもそも多段ドロップダウンをUpdatePanelの内側に置かない設計にする方が、長期保守では安定することが多いです。

最小構成で切り分ける:一度“動く形”を作ってから戻す

Bootstrapの問題は、原因が1つとは限りません。だからこそ、最小構成で「DropdownもModalも確実に動く」状態を作ってから、あなたのページ固有のCSS/JSやGridView周りを少しずつ戻すのが最短です。

最小構成の確認手順

  1. 新規の空aspx(または簡易ページ)を作る
  2. Bootstrapを1世代だけ読み込む(CSS/JS/依存関係をセットで)
  3. Bootstrap公式に近いDropdownとModalのHTMLを置く
  4. 動いたら、MasterPage・共通CSS・GridView・サブメニュー用CSS/JSの順に戻す
  5. 崩れ始めた地点が“競合の発生点”なので、そこを修正する

この手順は遠回りに見えて、実務では最速です。特に「モーダルのためにCSSだけ足したらドロップダウンが崩れた」ケースは、ページ全体が複雑で、原因の目星が付きにくいからです。

“混在”が疑わしいときの実務チェックリスト

以下のチェックは、慣れてくると数分で一通り確認できます。直らないときほど、順番に淡々と潰すのが効果的です。

  • bootstrap.min.css は1本だけか(同名でも中身が別バージョンのことがある)
  • bootstrap.min.js / bootstrap.bundle.min.js は1本だけか
  • Bootstrap 3/4なら jQueryが1本だけで、しかもBootstrapより先に読み込まれているか
  • Bootstrap 5なら bundleを使う(Popperの抜けを防ぐ)
  • HTMLの属性が世代に合っているか(data-toggle か data-bs-toggle か)
  • 自作CSSやテーマCSSで .dropdown-menu、.modal、.btn、a を強く上書きしていないか
  • 親コンテナに overflow: hidden が入っていないか(サブメニューが切れる)
  • z-indexを乱用していないか(モーダルとドロップダウンの重なり順が破綻)

よくある質問

BootstrapのCSSだけ入れたのにモーダルが「出る」のはなぜ?

答え:別の場所(MasterPage、共通テンプレート、別ライブラリ)で既にBootstrapのJSが読み込まれている可能性が高いです。そのJSが古い/別世代だと、CSSを追加した瞬間に全体の見た目が上書きされ、ドロップダウンだけ崩れることがあります。まずは“最終的に読み込まれているファイル”をページのソースとNetworkで確定させてください。

サブメニューがどうしても必要。Bootstrapだけで完結できる?

答え:小規模ならCSS調整でそれっぽく作れますが、クリック・ホバー・タッチ・キーボード操作まで含めて“破綻しない多段”をBootstrap標準だけで作るのは難易度が上がります。長期運用するなら、世代に合う多段対応の実装(プラグインや独自JS)を採用するか、UI自体を再設計する方が安全です。

GridViewのRowCommandでモーダルを開くと、一瞬表示されてすぐ消える

答え:ポストバックや部分更新でDOMが再描画されるタイミングと衝突している可能性があります。サーバー処理後にRegisterStartupScriptで開く方式に統一し、UpdatePanelが絡むならendRequestで再初期化するなど、表示タイミングを制御してください。また、ClientIDが変わって要素が見つからないケースも多いので、ClientIDModeの扱いも併せて確認します。

まとめ:直し方はシンプル、ただし「統一」と「切り分け」が重要

ASP.NET Web FormsでBootstrapのドロップダウン(サブメニュー)とモーダルが同じページでうまく動かないときは、まずBootstrapのCSS/JS/依存関係を1世代に統一し、二重読み込みを除去するのが最短です。次に、サブメニューは標準機能の範囲外になりやすい点を踏まえ、overflow/z-index/構造の観点で崩れを潰します。最後に、Web Forms特有のポストバック問題に合わせて、モーダル表示をクライアント側に寄せるか、サーバー処理後に確実に再表示する方式に整えれば、DropdownとModalの両立は安定して実現できます。

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次