



WithCodeMedia-1-pc
WithCodeMedia-2-pc
WithCodeMedia-3-pc
WithCodeMedia-4-pc




WithCodeMedia-1-sp
WithCodeMedia-2-sp
WithCodeMedia-3-sp
WithCodeMedia-4-sp









「z-indexを999にしたのに、要素が後ろに隠れたまま」「モーダルがヘッダーの下に潜り込んでしまう」——CSSのz-indexは、こうした重なり順のトラブルで最も頻繁に登場するプロパティのひとつです。数値を大きくすれば解決すると思いきや、まったく効果がないケースも多く、原因がつかめずに試行錯誤を繰り返した経験のある方も多いでしょう。
z-indexが思い通りに動かない根本的な理由は、「スタッキングコンテキスト(重なり文脈)」という概念を理解していないことにあります。このガイドでは、z-indexの基本から、position指定との関係、スタッキングコンテキストの仕組み、効かないときの原因と対処、そして固定ヘッダー・ドロップダウン・モーダルといった実践シーンでの正しい使い方まで、コピペで動くコード例を交えながら順を追って解説します。
初心者の方はz-indexの基礎から、中級者の方はスタッキングコンテキストや管理術のセクションから読み始めると効果的です。このガイドを読み終えた後には、z-indexで詰まることがほぼなくなるはずです。
CSSのレイアウトは、X軸(横)・Y軸(縦)の2次元平面に加えて、Z軸(画面の奥行き方向)という概念を持っています。z-indexは、この奥行き方向における要素の重なり順を制御するプロパティです。値が大きいほど手前(ユーザー側)に表示されます。
/* z-indexの基本的な書き方 */
.element {
position: relative; /* position指定が必須 */
z-index: 10; /* 整数値(負の値も可) */
}
.box-front {
position: absolute;
z-index: 2; /* 手前に表示 */
}
.box-back {
position: absolute;
z-index: 1; /* 奥に表示 */
}
.box-behind-all {
position: relative;
z-index: -1; /* 文書フローより奥 */
}
z-indexが最もよく引っかかる落とし穴が、position: static(初期値)の要素にはz-indexが適用されないという仕様です。CSSの仕様上、z-indexプロパティが有効になるのは「配置要素(positioned element)」——つまり position が relative / absolute / fixed / sticky のいずれかに設定された要素のみです。
/* NG: staticのままではz-indexは無視される */
.bad-example {
/* position: static; ← デフォルトのまま */
z-index: 9999; /* 効かない */
}
/* OK: positionを指定すると有効になる */
.good-example {
position: relative; /* または absolute / fixed / sticky */
z-index: 9999; /* 正しく効く */
}
position: relative はレイアウトへの影響が最小限で済むため、「重なり順を制御したいけれど位置はずらしたくない」という場面で最もよく使われます。top / left などのオフセットを指定しなければ、見た目上の位置は変わりません。
z-indexを明示的に指定しない場合、または同じ値の場合は、HTMLソース上で後に記述された要素が手前に表示されます。これを「後入れ優先(later in the DOM wins)」のルールと呼びます。
<div class="box box-1">BOX 1</div>
<div class="box box-2">BOX 2(こちらが前面)</div>
.box {
width: 200px;
height: 200px;
position: absolute;
}
.box-1 {
background: #d16176;
top: 0;
left: 0;
}
.box-2 {
background: #51322b;
top: 50px;
left: 50px;
/* z-indexなし→ HTMLで後の.box-2が前面に来る */
}
上記のコードでは .box-2 が .box-1 の上に重なります。これはz-indexを使わなくても成立する基本ルールです。
HTMLの順序に関係なく前面に持ってきたい場合に、z-indexで明示的な値を与えます。
.box-1 {
position: relative;
z-index: 2; /* HTMLでは先だが、z-indexで前面に */
}
.box-2 {
position: relative;
z-index: 1; /* HTMLでは後だが、z-indexで背面に */
}
z-indexを理解する上で最も重要な概念がスタッキングコンテキスト(Stacking Context・重なり文脈)です。スタッキングコンテキストとは、「その内部の要素の重なり順が、外部の要素に対して独立したグループ」を指します。
たとえるなら、スタッキングコンテキストは「透明なトレイ」のようなものです。トレイの中の要素はトレイ内でz-indexを競い合いますが、別のトレイの要素とは直接競争しません。トレイ自体のz-indexが、他のトレイや要素との相対的な重なり順を決めます。
<!-- ParentAがスタッキングコンテキストを形成 -->
<div class="parent-a"> <!-- z-index: 1 -->
<div class="child-a"></div> <!-- z-index: 9999 でも親の外には出られない -->
</div>
<!-- ParentBがスタッキングコンテキストを形成 -->
<div class="parent-b"> <!-- z-index: 2 -->
<div class="child-b"></div> <!-- z-index: 1 でもparent-aより前面 -->
</div>
.parent-a {
position: relative;
z-index: 1; /* スタッキングコンテキスト形成 */
}
.child-a {
position: relative;
z-index: 9999; /* parent-aの中での順位。parent-b自体には勝てない */
}
.parent-b {
position: relative;
z-index: 2; /* parent-aより大きいので、parent-b全体が前面に */
}
.child-b {
position: relative;
z-index: 1;
}
結果として、child-a(z-index: 9999)よりも child-b(z-index: 1)の方が前面に表示されます。これがスタッキングコンテキストの本質です。
スタッキングコンテキストは、以下の条件のいずれかを満たした要素に自動的に作られます。
このリストの中で特に見落としやすいのが、opacity / transform / filterです。次のセクションで詳しく掘り下げます。
最も多い原因です。前述の通り、position: static のままではz-indexは無視されます。
/* 直し方: positionを追加する */
.element {
position: relative; /* これを追加するだけで解決することが多い */
z-index: 10;
}
子要素にどれだけ大きなz-indexを指定しても、親のスタッキングコンテキストの「外」には出られません。モーダルが特定の要素の下に隠れてしまうケースの多くがこれです。
/* 問題のあるケース */
.parent {
position: relative;
z-index: 1; /* スタッキングコンテキストを形成してしまっている */
transform: translateZ(0); /* これも形成する */
}
.modal {
position: fixed;
z-index: 9999; /* 親のコンテキストに閉じ込められ、期待通りに動かない */
}
/* 直し方1: 親のz-indexをautoにして形成を解除 */
.parent {
position: relative;
/* z-indexを削除、またはautoに */
}
/* 直し方2: モーダルをbody直下に移動(DOM構造を変える) */
/* HTMLでモーダルを<body>の直前の子要素として配置する */
A要素とB要素のz-indexを比較したいのに、それぞれが別の親スタッキングコンテキストに属している場合、z-indexの数値ではなく親コンテキスト同士の重なり順が結果を決めます。
/* 直し方: 両要素を同じスタッキングコンテキストの兄弟にする */
/* HTMLの構造を見直し、共通の親の直接の子にする */
.wrapper {
/* スタッキングコンテキストを作らない */
}
.element-a {
position: relative;
z-index: 2;
}
.element-b {
position: relative;
z-index: 1;
}
古いコードでは float を使ったレイアウトに z-index が絡んで意図しない重なりが起きることがあります。floatされた要素は通常フローの上に配置されますが、z-indexの競争は配置祖先と比べて判定されます。モダンなFlexbox・Gridへの移行で多くは解決します。
position: fixed の要素は「ビューポートを基準に固定」されますが、親にtransformやfilterがかかっているとその親を基準に変わってしまうという仕様があります(後述)。この場合もDOM構造の見直しが根本的な解決策です。
CSSアニメーションや視覚効果でよく使うopacity・transform・filterは、値を変更するだけでスタッキングコンテキストを生成します。これを知らずに使うと、意図しない重なり順のバグが発生します。
/* これらはすべてスタッキングコンテキストを生成する */
.fade-in {
opacity: 0.99; /* 1未満なら0.99でもコンテキスト生成 */
}
.slide-in {
transform: translateX(0); /* noneでなければ生成 */
}
.blur-bg {
filter: blur(0px); /* noneでなければ生成 */
}
.gpu-hint {
will-change: transform; /* transform/opacity/filterを指定するとコンテキスト生成 */
}
特にはまりやすいのが「position: fixed の要素が、transformが指定された祖先要素の中にいる」ケースです。本来fixedはビューポート基準のはずですが、transformを持つ祖先が新しい「包含ブロック」になってしまうため、ビューポート固定が崩れます。
<!-- 問題のある構造 -->
<div class="page-wrapper"> <!-- transform: translateZ(0) などがある -->
<header class="fixed-header"></header> <!-- position: fixed が効かなくなる -->
<main>...</main>
</div>
/* 問題のある.page-wrapper */
.page-wrapper {
transform: translateZ(0); /* GPU高速化のつもりがfixed要素を壊す */
}
/* 直し方: fixed要素はtransformのない祖先の直下に置く */
/* または.page-wrapperからtransformを取り除き、
GPUヒントが必要な要素だけに個別に指定する */
スタッキングコンテキストを「z-indexを使わずに」意図的に作りたい場面があります。そこで使えるのが isolation: isolate です。コンポーネントを外部のz-indexから独立させたい場合に有効です。
.component {
isolation: isolate; /* 新しいスタッキングコンテキストを明示的に形成 */
/* position不要、z-indexも不要 */
}
/* コンポーネント内の要素は外部のz-indexと競合しなくなる */
.component .inner-element {
position: relative;
z-index: 100; /* このコンテキスト内でのみ有効 */
}
固定ヘッダーは最も基本的なz-indexの使用例です。スクロールしてもコンテンツの上に表示され続けるよう、十分に高いz-indexを与えます。
.site-header {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 60px;
background: #fff;
z-index: 100; /* コンテンツより前面 */
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
/* ヘッダー分の余白をmainに設定 */
.site-main {
padding-top: 60px;
}
ナビゲーションのドロップダウンメニューは、ヘッダーの子要素として配置されるため、ヘッダーのスタッキングコンテキスト内に含まれます。ヘッダー自体がコンテキストを形成している場合、ドロップダウンはヘッダー内のz-indexで制御します。
<header class="site-header">
<nav class="global-nav">
<ul>
<li class="nav-item has-dropdown">
<a href="#">メニュー</a>
<ul class="dropdown-menu">
<li><a href="#">サブメニュー1</a></li>
<li><a href="#">サブメニュー2</a></li>
</ul>
</li>
</ul>
</nav>
</header>
.site-header {
position: fixed;
z-index: 100;
/* ここでz-indexを指定するとスタッキングコンテキストが生成される */
/* ドロップダウンはこのコンテキスト内に含まれる */
}
.nav-item {
position: relative; /* ドロップダウンの基準点 */
}
.dropdown-menu {
position: absolute;
top: 100%;
left: 0;
z-index: 10; /* ヘッダーコンテキスト内での前面配置 */
display: none;
background: #fff;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
min-width: 200px;
}
.nav-item:hover .dropdown-menu {
display: block;
}
モーダルはページの最前面に表示する必要があります。最も確実な実装方法は、モーダルをbodyの直接の子要素として配置することです。これにより、どのスタッキングコンテキストにも閉じ込められません。
<!-- bodyの直下に配置するのがベストプラクティス -->
<body>
<div id="app">
<!-- メインコンテンツ -->
</div>
<!-- モーダルはapp外、body直下 -->
<div class="modal-overlay" id="modal">
<div class="modal-dialog">
<button class="modal-close">×</button>
<div class="modal-content">
<p>モーダルの内容</p>
</div>
</div>
</div>
</body>
.modal-overlay {
position: fixed;
inset: 0; /* top/right/bottom/left: 0 の短縮形 */
background: rgba(0, 0, 0, 0.6);
z-index: 1000; /* ヘッダー(100)より大きい値 */
display: flex;
align-items: center;
justify-content: center;
/* 非表示時 */
opacity: 0;
pointer-events: none;
transition: opacity 0.3s;
}
.modal-overlay.is-open {
opacity: 1;
pointer-events: auto;
}
.modal-dialog {
position: relative;
background: #fff;
border-radius: 8px;
padding: 32px;
max-width: 560px;
width: 90%;
max-height: 80vh;
overflow-y: auto;
}
.modal-close {
position: absolute;
top: 12px;
right: 16px;
background: none;
border: none;
font-size: 24px;
cursor: pointer;
color: #51322b;
}
また、HTML標準の <dialog> 要素(2026年時点でほぼ全モダンブラウザ対応)を使うと、ブラウザが最上位レイヤーに自動的に配置するため、z-indexの管理から解放されるケースが増えています。
<!-- dialog要素を使う現代的な書き方 -->
<dialog id="my-modal">
<p>モーダルの内容</p>
<button id="close-btn">閉じる</button>
</dialog>
<button id="open-btn">モーダルを開く</button>
/* dialog要素はブラウザが最上位レイヤーに配置するため
z-indexを気にする必要がほぼない */
dialog {
border: none;
border-radius: 8px;
padding: 32px;
max-width: 560px;
width: 90%;
}
dialog::backdrop {
background: rgba(0, 0, 0, 0.6);
}
画面右下に表示するトースト通知や、要素のホバー時に表示するツールチップも固定・絶対配置とz-indexの組み合わせで実装します。
/* トースト通知 */
.toast-container {
position: fixed;
bottom: 24px;
right: 24px;
z-index: 900; /* ヘッダー(100)とモーダル(1000)の間 */
display: flex;
flex-direction: column;
gap: 8px;
}
.toast {
background: #51322b;
color: #fff;
padding: 12px 20px;
border-radius: 6px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
animation: slide-in 0.3s ease;
}
@keyframes slide-in {
from { transform: translateX(120%); }
to { transform: translateX(0); }
}
/* ツールチップ */
.tooltip-wrapper {
position: relative;
display: inline-block;
}
.tooltip {
position: absolute;
bottom: calc(100% + 8px);
left: 50%;
transform: translateX(-50%);
background: #333;
color: #fff;
padding: 6px 12px;
border-radius: 4px;
white-space: nowrap;
font-size: 0.8rem;
z-index: 50;
pointer-events: none;
opacity: 0;
transition: opacity 0.2s;
}
.tooltip-wrapper:hover .tooltip {
opacity: 1;
}
プロジェクトが大きくなると、あちこちに散らばった z-index: 9999 や z-index: 999999 が衝突したり、どれが正しい値かわからなくなったりします。CSS変数(カスタムプロパティ)を使ってz-indexを一元管理するのがモダンなベストプラクティスです。
/* :root でz-indexをまとめて定義 */
:root {
--z-below: -1;
--z-base: 0;
--z-raised: 10; /* カード・小要素の重なり */
--z-dropdown: 100; /* ドロップダウン */
--z-sticky: 200; /* 固定ヘッダー・スティッキー要素 */
--z-overlay: 300; /* オーバーレイ・背景幕 */
--z-modal: 400; /* モーダルダイアログ */
--z-toast: 500; /* トースト・通知 */
--z-tooltip: 600; /* ツールチップ */
}
/* 各コンポーネントで変数を参照 */
.site-header {
position: fixed;
z-index: var(--z-sticky);
}
.modal-overlay {
position: fixed;
z-index: var(--z-modal);
}
.toast-container {
position: fixed;
z-index: var(--z-toast);
}
値を変更したいときは :root の定義を1行直すだけで全体に反映されます。また、ファイルの先頭を見ればプロジェクトのz-index設計が一目瞭然になります。
Sass/SCSSを採用しているプロジェクトでは、マップ(Map)を使った管理もよく使われます。
/* _z-index.scss */
$z-layers: (
"below": -1,
"base": 0,
"raised": 10,
"dropdown": 100,
"sticky": 200,
"overlay": 300,
"modal": 400,
"toast": 500,
"tooltip": 600
);
/* 取り出すfunctionを定義 */
@function z($layer) {
@return map-get($z-layers, $layer);
}
/* 使用例 */
.site-header {
z-index: z("sticky"); /* 200 */
}
.modal-overlay {
z-index: z("modal"); /* 400 */
}
z-indexの値は「1, 2, 3…」と連番にするより、10や100刻みにしておくと、後から「この要素を既存の2つの間に挟みたい」というケースで柔軟に対応できます。z-index: 99999 のような極端な値は、それが必要になった時点で設計の見直しサインと捉えましょう。
Chrome DevTools では、「Elements」パネルで要素を選択し「Computed」タブの「z-index」を確認できます。また、Firefox DevTools では「3Dビュー」機能でページの重なり構造を立体的に可視化できます(DevTools → その他ツール → レイヤー)。
ほぼ確実に、対象要素またはその祖先要素がスタッキングコンテキストに閉じ込められています。まず、その要素の親要素をすべて遡って、position + z-index / opacity / transform / filter が指定されている要素がないか確認してください。見つかったら、その親からz-indexを削除するか、対象要素をその親の外のDOMに移動させることで解決します。
z-index: -1 を指定すると、その要素はスタッキングコンテキスト内の「通常フローの要素」よりも後ろ(奥)に配置されます。背景の装飾的な要素をコンテンツの後ろに配置したいときに使います。ただし、スタッキングコンテキストを持つ祖先の背景よりは前面に留まるため、さらに奥に隠したい場合は、DOMの構造を変えるか isolation: isolate を使って調整します。
はい、作ります。opacity: 0 は視覚的には見えませんが、CSSの仕様上は「opacity が 1 未満の要素」に該当するため、スタッキングコンテキストを生成します。また、opacity: 0 にしただけではポインターイベントも受け付け続けます。完全に非表示にしたい場合は visibility: hidden や display: none との組み合わせを検討してください。
Reactなどでは、モーダル・ドロップダウン・トーストなどのUI要素をReact Portal(ReactDOM.createPortal)を使ってbody直下にレンダリングするパターンが標準的です。これにより、コンポーネントがどのネスト階層に書かれていても、DOM上はbodyの直接の子として配置されるため、スタッキングコンテキストの問題を回避できます。z-indexの値はCSS変数またはデザイントークンで管理し、スタイルシートに集約します。
直接的なSEOへの影響はありません。ただし、z-indexの誤設定でコンテンツが視覚的に隠れてしまっている場合、Googleのレンダリングエンジン(Chromiumベース)がそのコンテンツをどう評価するかは非自明です。重要なコンテンツがCSSで不可視になっている状態は避けるべきです。また、モーダルや固定ヘッダーがCore Web Vitals(特にCLS・INP)に影響するケースもあるため、パフォーマンス面での配慮も必要です。
z-indexは単純に見えて、スタッキングコンテキストという概念を理解しないと思い通りに動かせないプロパティです。「数値を大きくすれば前に来る」という単純なルールは、スタッキングコンテキストの境界を越えた途端に崩れます。
あわせて読みたい:CSSだけでモーダル(ポップアップ)を作る方法

副業・フリーランスが主流になっている今こそ、自らのスキルで稼げる人材を目指してみませんか?
未経験でも心配することはありません。初級コースを受講される方の大多数はプログラミング未経験です。まずは無料カウンセリングで、悩みや不安をお聞かせください!
WithCodeでWeb制作を習得後、フリーランスエンジニアとして活動。HTML/CSS・JavaScript・WordPress案件を中心に年間20件以上の制作実績を持つ。「難しい技術をわかりやすく」をモットーに、初心者〜中級者向けの技術記事を執筆。副業・フリーランス独立を目指す方に向けた情報発信に注力している。
公式サイト より
今すぐ
無料カウンセリング
を予約!