猫の手ツール

Step 1: Web Basics

07. モーダル画面を作る時の
z-index の罠

保存ガイドやメニューを重ねるときは、数値だけでなく祖先のスタッキングコンテキストを確認します。猫の手ツールではメニューと保存ガイドに異なるレイヤー値を設定しています。現在のDOMとCSSを出発点に、説明用の小さい例で重なり方を調べます。

1. Webページにある「見えない奥行き」

Webページはブラウザに表示される平面的なものですが、CSSには Z軸(奥行き) という概念が存在します。この重なり順を制御するのが z-index プロパティです。

通常、HTML要素はコードの後に書かれたものほど手前に表示されますが、モーダルやドロップダウンメニューのように「強制的に一番手前に持ってきたい」要素がある場合、この z-index を使ってレイヤーの順序を明示的に指定する必要があります。

2. 数値が大きければ良い、というわけではない?

初心者がやりがちなのが、要素が手前に来ないからといって z-index: 9999; のように適当に大きな数字を設定することです。しかし、これでも解決しない場合があります。

z-indexは、位置指定された要素に加え、FlexboxやGridの子要素にも適用されます。そのため「position: staticなら必ず無効」とは言えません。値を比較するときは、どのスタッキングコンテキストに属するかも確認します。

Tailwind CSS でのレイヤー管理例

次は、同じスタッキングコンテキストの中で背景より説明パネルを前に置く、説明用の例です。

【正解】オーバーレイとモーダルの関係

<!-- 背後の半透明カバー (z-40) -->
<div class="fixed inset-0 bg-slate-900/60 backdrop-blur-sm z-40"></div>

<!-- モーダルコンテンツ (z-50) -->
<div class="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 z-50 bg-white rounded-2xl p-8">
    <h3>画像を保存する方法</h3>
    <p>画像を長押しして「写真に保存」を選択してください。</p>
</div>

3. 最大の落とし穴「スタッキング文脈」

数値を変えても直らない場合に確認したいのが、スタッキングコンテキストです。

これは、ある要素が「新しいレイヤーの親」になってしまう仕組みです。例えば、親要素Aに opacity: 0.9 や transform が設定されていると、その中の子要素Bにどれだけ大きな z-index を設定しても、親要素Aのレイヤーの枠組みを超えて手前に出ることはできません。

例えば、親Aのz-indexが1、親Bが2で、両者が同じ親の中の独立したスタッキングコンテキストだとします。Aの子を9999にしても、Bの上には出られません。子だけでなく、祖先のtransform、opacity、positionとz-indexを順に確認するのが近道です。

4. 注意点:z-index の管理を破綻させないコツ

大規模な開発になるほど、z-index の数値はカオスになりがちです。使う値と役割を決めて共有すると、後の変更を追いやすくなります。

  • 1刻みで使わない: Tailwind のように 10, 20, 30... と間隔をあけて定義する。
  • 変数の活用: プロジェクト側で定義したCSS変数やユーティリティを使い、モーダル・オーバーレイ・ヘッダーの順序を共有する。z-modalなどの名前はTailwindの標準クラスではなく、追加定義が必要です。
  • DOMの位置: モーダル要素などは、可能な限り <body> の直下など、階層の浅い場所に配置する(スタッキング文脈の影響を最小限にするため)。

見た目の重なりと、モーダルとしての操作を確認する

前掲のdivの例は重なり方だけを示しています。実際のモーダルには、開いたときのフォーカス、背後の操作制限、Escapeで閉じる操作、閉じた後のフォーカス復帰も必要です。

HTMLのdialog要素をshowModal()で開く方法なら、通常のz-indexの競争とは別のトップレイヤーに表示できます。以下の要素を置いた後でスクリプトを実行してください。

<button id="open-guide" type="button">保存方法を見る</button>
<dialog id="save-guide" aria-labelledby="guide-title">
  <h2 id="guide-title">保存方法</h2>
  <p>表示された画像を確認して保存してください。</p>
  <form method="dialog"><button>閉じる</button></form>
</dialog>
const opener = document.getElementById('open-guide');
const guide = document.getElementById('save-guide');
if (opener && guide instanceof HTMLDialogElement) {
  opener.addEventListener('click', () => guide.showModal());
  guide.addEventListener('close', () => opener.focus());
}

本番の保存ガイドは、親ごとz-index 100へ置く

履歴書写真メーカーでは、メニュー用の背景がz-40、スマホのヘッダーがz-50、保存ガイドの親がz-[100]です。保存ガイドの親はbody直下にあり、その内部の背景と白い説明パネルが同じグループになります。

<div id="iphone-save-modal"
     class="fixed inset-0 z-[100] hidden flex items-center justify-center px-4">
  <!-- 背景と説明パネルを、この親の中へ置く -->
</div>

これは本番の親要素を抜粋したものです。前掲のz-40/z-50は重なりを学ぶ説明例で、本番の保存ガイドの値とは分けてください。以下のdialog例も別の教材で、本番がdialogへ置き換わっているという意味ではありません。証明写真の制作解説から実ツールの保存画面へ進めます。

5. まとめ:実務での役立ち

「猫の手ツール」では、iPhoneユーザー向けの保存ダイアログや、サイドメニューの表示にこの z-index の設計を活かしています。

見た目の順序だけでなく、フォーカス、Escape、背景の操作制限を合わせて確認することが、モーダルの使いやすさにつながります。

参考資料

制作時に確かめたこと