要素どうしが重なったとき、どちらを前面に出すかを決めるのが z-index プロパティです。モーダルを最前面に出したい、固定ヘッダーをコンテンツの上にかぶせたい、といった場面でよく使います。ところが「z-index を指定したのに重なり順が変わらない」「9999 にしても上に来ない」というつまずきも非常に多いプロパティです。この記事では、z-index が効く条件、値による重なり順の決まり方、そしてつまずきの正体である「スタッキングコンテキスト」の考え方を、初心者の方にも分かるように順を追って解説します。実際に動くデモも交えて確認していきましょう。
目次
z-index とは何か
z-index は、重なり合った要素の「奥行き方向(手前と奥)」の順番を指定する CSS プロパティです。画面は横(x 軸)と縦(y 軸)の平面ですが、要素が重なると、その重なりは画面の手前から奥へ向かう z 軸方向の順番として扱われます。z-index の「z」はこの軸を指しています。値が大きいほど手前(上)に、小さいほど奥(下)に配置されます。
通常、要素は HTML に書かれた順番で重なり、あとから書いた要素ほど手前に表示されます。この自然な重なり順を上書きして、好きな前後関係に並べ替えたいときに z-index を使います。ただし、このプロパティには「効くための条件」があり、そこを知らないとうまく動きません。まずはその条件から押さえましょう。
z-index は position が static 以外でないと効かない
最初に必ず覚えておきたいのは、z-index は position が static 以外の要素にしか効かないという点です。position の初期値は static なので、何も指定していない素の要素に z-index を書いても、その指定は無視されます。z-index を効かせるには、対象の要素に relative / absolute / fixed / sticky のいずれかを指定しておく必要があります。
/* これは効かない:position が static(初期値)のまま */
.box-a {
z-index: 10;
}
/* これは効く:position を static 以外にしている */
.box-b {
position: relative; /* relative / absolute / fixed / sticky のいずれか */
z-index: 10;
}
「z-index を書いたのにまったく反応しない」という場合、その大半はこの position の指定漏れが原因です。position: relative; は要素の見た目の位置を変えずに済むため、重なり順だけを調整したいときの定番として覚えておくとよいでしょう。
値の大小で重なり順が決まる
z-index には整数を指定します。同じ並び(同じ親の文脈)の中で比較したとき、値が大きい要素ほど手前に、小さい要素ほど奥に表示されます。負の値も指定でき、たとえば z-index: -1; にすると、z-index を指定していない通常の要素よりも奥へ送ることができます。背景的な装飾を本文の後ろに敷きたいときなどに使えます。
下のデモでは、赤・緑・青の 3 つのボックスを少しずつずらして重ねています。それぞれに z-index を 1 / 3 / 2 と指定しているため、HTML 上の順番(赤→緑→青)とは関係なく、値の大きい緑がいちばん手前、値の小さい赤がいちばん奥に表示されます。CSS タブを開いて、各ボックスにどの値を指定しているか確認してみてください。値を書き換えれば、重なり順がどう変わるかも試せます。
<div class="stage">
<div class="box box--red">z-index: 1</div>
<div class="box box--green">z-index: 3</div>
<div class="box box--blue">z-index: 2</div>
</div>
.stage {
position: relative;
height: 220px;
background-color: #f3f4f6;
font-family: sans-serif;
}
/* 3つのボックスを少しずつずらして重ねる */
.box {
position: absolute; /* z-index を効かせるため static 以外にする */
width: 120px;
height: 120px;
display: flex;
align-items: center;
justify-content: center;
color: #ffffff;
font-weight: bold;
border-radius: 10px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);
}
/* 赤:いちばん奥(値が小さい) */
.box--red {
top: 40px;
left: 40px;
background-color: #ef4444;
z-index: 1;
}
/* 緑:いちばん手前(値が大きい) */
.box--green {
top: 70px;
left: 100px;
background-color: #22c55e;
z-index: 3;
}
/* 青:赤と緑の中間 */
.box--blue {
top: 100px;
left: 160px;
background-color: #3b82f6;
z-index: 2;
}
ここで大切なのは、この値の比較が「同じ階層(同じ親の文脈)の中」でしか行われないという点です。次の節で説明するスタッキングコンテキストを理解すると、「値を大きくしたのに上に来ない」という現象の理由がはっきり分かるようになります。
スタッキングコンテキストという考え方
z-index を理解するうえで欠かせないのが、スタッキングコンテキスト(重ね合わせコンテキスト)という概念です。少し難しく聞こえますが、「重なり順を比較する単位(グループ)」だと考えると分かりやすくなります。z-index の値は、世界中のすべての要素で一斉に比較されるわけではなく、同じスタッキングコンテキストに属する要素どうしの中でだけ比較されます。
イメージとしては、本棚を思い浮かべてください。棚そのものの並び順(手前か奥か)はまず棚どうしで決まり、その棚の中の本の順番は、あくまでその棚の中だけで決まります。どれだけ手前の本に大きな番号を振っても、その本が奥の棚を飛び越えて手前に出てくることはありません。スタッキングコンテキストもこれと同じで、ある文脈の中の z-index は、その文脈の外の要素とは直接比較されないのです。
では、このスタッキングコンテキストはどういうときに作られるのでしょうか。ページ全体のいちばん外側(ルート要素)が最初のスタッキングコンテキストになるほか、ある要素に特定のプロパティが付くと、その要素を起点に新しいスタッキングコンテキストが生まれます。代表的なきっかけを表にまとめます。
| スタッキングコンテキストが作られる主な条件 | 補足 |
|---|---|
position が relative / absolute で、z-index が auto 以外 | もっともよく出会うパターン |
position: fixed; または position: sticky; | z-index の指定がなくても作られる |
opacity が 1 未満 | 半透明にしただけで文脈が生まれる |
transform が none 以外 | translate などを当てた要素も該当 |
filter が none 以外 | blur() などを当てた要素も該当 |
注目してほしいのは、opacity や transform といった、一見すると重なり順とは関係なさそうなプロパティでも新しいスタッキングコンテキストが作られる点です。これが、後述する「z-index を大きくしても上に来ない」というトラブルの大きな原因になります。
実践例:モーダル・固定ヘッダー・ドロップダウン
ここからは、z-index が実際に使われる代表的な場面を見ていきます。いずれも「ある要素を、ほかの要素より手前に確実に出す」ための指定です。
モーダルとオーバーレイを前面に出す
モーダル(ポップアップ)を表示するときは、背景を暗くする半透明のオーバーレイと、その上に乗るモーダル本体の 2 つを、ページの最前面に重ねます。オーバーレイをページ全体に広げるために position: fixed; を使い、z-index でほかのコンテンツより手前に出します。モーダル本体には、さらに大きな値を与えてオーバーレイより前面に置きます。
/* 背景を覆う半透明のオーバーレイ */
.overlay {
position: fixed;
inset: 0; /* top/right/bottom/left をすべて 0 に */
background-color: rgba(0, 0, 0, 0.5);
z-index: 1000;
}
/* オーバーレイの上に乗るモーダル本体 */
.modal {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 1001; /* オーバーレイより手前に */
}
ヘッダーを最前面に固定する
スクロールしても画面上部に貼り付くヘッダーは、position: sticky; または position: fixed; で固定します。ただし固定しただけだと、後ろに流れてくる本文や画像がヘッダーの上に重なってしまうことがあります。そこで z-index を指定し、ヘッダーを常にコンテンツより手前に置きます。
.site-header {
position: sticky;
top: 0; /* 上端に貼り付く */
z-index: 100; /* 本文より手前に表示する */
}
ドロップダウンメニューを上に表示する
ナビゲーションのドロップダウンや、入力欄の下に出る候補リストは、開いたときに後続のコンテンツの上にかぶせて表示する必要があります。親メニューを position: relative; にして基準にし、展開する子メニューを position: absolute; で重ね、z-index で周囲より手前に出します。
.menu {
position: relative; /* 子メニューの位置の基準になる */
}
/* 展開するメニュー本体 */
.menu__dropdown {
position: absolute;
top: 100%; /* 親のすぐ下に出す */
left: 0;
z-index: 10; /* 後続のコンテンツより手前に */
}
これらの例で使う z-index の値は、たとえばヘッダーは 100、モーダルは 1000 番台、というように、役割ごとにある程度まとまった範囲を決めておくと管理しやすくなります。値は連番である必要はなく、あとから間に別の要素を挟めるよう、ある程度間隔を空けておくのがおすすめです。
z-index を大きくしても上に来ない原因
z-index でいちばん多いつまずきが、「効かない」「値を大きくしても前面に出ない」というものです。原因はいくつかのパターンに分かれます。心当たりがないか、順に確認してみてください。
position が指定されていない
前述のとおり、z-index は position が static(初期値)のままの要素には効きません。値をいくら大きくしても、まったく反応しないように見えます。z-index が無視されているように感じたら、まずその要素に position: relative; などが指定されているかを確認しましょう。flex や grid のアイテムは例外的に position: static; でも z-index が効きますが、基本は「position 必須」と覚えておけば間違いありません。
別のスタッキングコンテキストに閉じ込められている
「z-index: 9999 にしたのに、特定の要素の上にだけ出てこない」という現象の正体は、たいていこれです。前述のとおり、親要素に opacity: 0.9; や transform: translateZ(0); などが付いていると、そこに新しいスタッキングコンテキストが生まれます。すると子要素の z-index は、その親の文脈の中でしか比較されなくなります。
たとえば z-index: 5 の親 A の中に z-index: 9999 の子があり、外側に z-index: 10 の要素 B があるとします。子の 9999 は親 A の中での順位でしかないため、A 全体が 5 として扱われ、結果として子は B(10)よりも奥に沈んでしまいます。値の大小ではなく、まず親(文脈)どうしの順番で勝負が決まるからです。解決するには、子の値をいじるのではなく、原因になっている親のスタッキングコンテキスト(不要な opacity や transform を外す、親自身の z-index を上げる、など)を見直す必要があります。
値が同じで、あとに書いた要素が上になっている
重なる要素の z-index が同じ値(あるいはどちらも未指定)の場合、HTML 上であとに書かれた要素のほうが手前に表示されます。「同じ値にしたのに思った要素が前に来ない」というときは、この描画順が効いています。意図した前後関係にしたいなら、片方の z-index を明示的に大きくするか、HTML 上の記述順を入れ替えて対応します。
まとめ
z-index は要素の重なり順を制御するプロパティで、値が大きいほど手前に表示されます。ただし効かせるには position が static 以外であることが前提で、これが指定漏れの定番です。値の比較は同じスタッキングコンテキストの中でだけ行われるため、親に z-index はもちろん opacity や transform が付くと、子の z-index をどれだけ大きくしてもその親の枠を越えられません。「9999 にしても上に来ない」ときは、子の値ではなく親の文脈を疑うのが解決の近道です。モーダル・固定ヘッダー・ドロップダウンのように役割ごとに値の範囲を決めておくと、重なり順を見通しよく管理できます。