WithCodeMedia-1-pc
previous arrowprevious arrow
next arrownext arrow

WithCodeMedia-1-sp
previous arrowprevious arrow
next arrownext arrow

【2026年版】CSS z-indexの使い方完全ガイド|重なり順・効かない原因・スタッキングコンテキスト

「z-indexを999にしたのに、要素が後ろに隠れたまま」「モーダルがヘッダーの下に潜り込んでしまう」——CSSのz-indexは、こうした重なり順のトラブルで最も頻繁に登場するプロパティのひとつです。数値を大きくすれば解決すると思いきや、まったく効果がないケースも多く、原因がつかめずに試行錯誤を繰り返した経験のある方も多いでしょう。

z-indexが思い通りに動かない根本的な理由は、「スタッキングコンテキスト(重なり文脈)」という概念を理解していないことにあります。このガイドでは、z-indexの基本から、position指定との関係、スタッキングコンテキストの仕組み、効かないときの原因と対処、そして固定ヘッダー・ドロップダウン・モーダルといった実践シーンでの正しい使い方まで、コピペで動くコード例を交えながら順を追って解説します。

初心者の方はz-indexの基礎から、中級者の方はスタッキングコンテキストや管理術のセクションから読み始めると効果的です。このガイドを読み終えた後には、z-indexで詰まることがほぼなくなるはずです。

目次

z-indexとは何か——基本と役割

画面奥行き方向(Z軸)を操るプロパティ

CSSのレイアウトは、X軸(横)・Y軸(縦)の2次元平面に加えて、Z軸(画面の奥行き方向)という概念を持っています。z-indexは、この奥行き方向における要素の重なり順を制御するプロパティです。値が大きいほど手前(ユーザー側)に表示されます。

/* z-indexの基本的な書き方 */
.element {
  position: relative; /* position指定が必須 */
  z-index: 10;        /* 整数値(負の値も可) */
}

指定できる値の種類

  • 整数値(正): z-index: 1、z-index: 100 など。値が大きいほど前面。
  • 整数値(負): z-index: -1 など。通常の文書フローより後ろに配置。
  • auto(初期値): 親のスタッキングコンテキストから値を継承した扱いになり、新しいスタッキングコンテキストを作らない。
.box-front {
  position: absolute;
  z-index: 2; /* 手前に表示 */
}

.box-back {
  position: absolute;
  z-index: 1; /* 奥に表示 */
}

.box-behind-all {
  position: relative;
  z-index: -1; /* 文書フローより奥 */
}

position指定がないとz-indexは効かない

なぜpositionが必要なのか

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 などのオフセットを指定しなければ、見た目上の位置は変わりません。

positonとz-indexの対応表

  • static: z-indexは無効(初期値)
  • relative: z-indexが有効。元の位置から相対的にずらせる
  • absolute: z-indexが有効。直近の配置祖先を基準に配置
  • fixed: z-indexが有効。ビューポートを基準に固定配置
  • sticky: z-indexが有効。スクロールに応じてstaticとfixedを切り替え

重なり順の基本ルール——HTMLの順序との関係

z-indexが同じ(またはauto)の場合はHTMLの後ろが上

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を使わなくても成立する基本ルールです。

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)の方が前面に表示されます。これがスタッキングコンテキストの本質です。

スタッキングコンテキストが作られる条件

スタッキングコンテキストは、以下の条件のいずれかを満たした要素に自動的に作られます。

  • ルート要素(<html>)
  • position が relative / absolute / fixed / sticky かつ z-index が auto 以外の整数値を持つ要素
  • opacity が 1 未満の要素
  • transform が none 以外の要素
  • filter が none 以外の要素
  • will-change に opacity / transform / filter のいずれかを指定した要素
  • isolation: isolate を指定した要素
  • mix-blend-mode が normal 以外の要素
  • contain に layout / paint / strict / content を含む要素

このリストの中で特に見落としやすいのが、opacity / transform / filterです。次のセクションで詳しく掘り下げます。

z-indexが効かない5つの原因と対処法

原因1: positionが指定されていない

最も多い原因です。前述の通り、position: static のままではz-indexは無視されます。

/* 直し方: positionを追加する */
.element {
  position: relative; /* これを追加するだけで解決することが多い */
  z-index: 10;
}

原因2: 親要素がスタッキングコンテキストを形成している

子要素にどれだけ大きな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>の直前の子要素として配置する */

原因3: 比較対象の要素が別のスタッキングコンテキストにいる

A要素とB要素のz-indexを比較したいのに、それぞれが別の親スタッキングコンテキストに属している場合、z-indexの数値ではなく親コンテキスト同士の重なり順が結果を決めます。

/* 直し方: 両要素を同じスタッキングコンテキストの兄弟にする */
/* HTMLの構造を見直し、共通の親の直接の子にする */

.wrapper {
  /* スタッキングコンテキストを作らない */
}

.element-a {
  position: relative;
  z-index: 2;
}

.element-b {
  position: relative;
  z-index: 1;
}

原因4: floatとの競合

古いコードでは float を使ったレイアウトに z-index が絡んで意図しない重なりが起きることがあります。floatされた要素は通常フローの上に配置されますが、z-indexの競争は配置祖先と比べて判定されます。モダンなFlexbox・Gridへの移行で多くは解決します。

原因5: DOMの外に出るべき要素が内側にいる

position: fixed の要素は「ビューポートを基準に固定」されますが、親にtransformやfilterがかかっているとその親を基準に変わってしまうという仕様があります(後述)。この場合もDOM構造の見直しが根本的な解決策です。

opacity / transform / filter / will-changeが作る「意図しないスタッキングコンテキスト」

透明にしただけでコンテキストが生まれる

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を指定するとコンテキスト生成 */
}

transformとposition: fixedの組み合わせ問題

特にはまりやすいのが「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ヒントが必要な要素だけに個別に指定する */

isolation: isolateで意図的にコンテキストを作る

スタッキングコンテキストを「z-indexを使わずに」意図的に作りたい場面があります。そこで使えるのが isolation: isolate です。コンポーネントを外部のz-indexから独立させたい場合に有効です。

.component {
  isolation: isolate; /* 新しいスタッキングコンテキストを明示的に形成 */
  /* position不要、z-indexも不要 */
}

/* コンポーネント内の要素は外部のz-indexと競合しなくなる */
.component .inner-element {
  position: relative;
  z-index: 100; /* このコンテキスト内でのみ有効 */
}

実践: 固定ヘッダー・ドロップダウン・モーダルの重なり順設計

固定ヘッダー(position: fixed)

固定ヘッダーは最も基本的な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">&times;</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値の設計・管理術

マジックナンバーは避ける——CSS変数(カスタムプロパティ)で管理

プロジェクトが大きくなると、あちこちに散らばった 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設計が一目瞭然になります。

SCSSを使う場合はマップで管理

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の値には10刻み・100刻みを使う

z-indexの値は「1, 2, 3…」と連番にするより、10や100刻みにしておくと、後から「この要素を既存の2つの間に挟みたい」というケースで柔軟に対応できます。z-index: 99999 のような極端な値は、それが必要になった時点で設計の見直しサインと捉えましょう。

よくあるミスとデバッグのコツ

ブラウザ開発者ツールでスタッキングコンテキストを確認する

Chrome DevTools では、「Elements」パネルで要素を選択し「Computed」タブの「z-index」を確認できます。また、Firefox DevTools では「3Dビュー」機能でページの重なり構造を立体的に可視化できます(DevTools → その他ツール → レイヤー)。

よくあるミスのチェックリスト

  • positionを付け忘れていないか
  • 比較したい要素が同じスタッキングコンテキスト内にいるか
  • 親要素にtransform / opacity / filterがかかっていないか
  • position: fixed の要素の祖先にtransformがないか
  • z-indexの値が意図した通りに設計されているか(マジックナンバーになっていないか)
  • will-changeを不必要に使っていないか

よくある質問(FAQ)

Q. z-index: 9999 にしても前に来ない。どうすれば良いですか?

ほぼ確実に、対象要素またはその祖先要素がスタッキングコンテキストに閉じ込められています。まず、その要素の親要素をすべて遡って、position + z-index / opacity / transform / filter が指定されている要素がないか確認してください。見つかったら、その親からz-indexを削除するか、対象要素をその親の外のDOMに移動させることで解決します。

Q. z-index: -1 を使うとどうなりますか?

z-index: -1 を指定すると、その要素はスタッキングコンテキスト内の「通常フローの要素」よりも後ろ(奥)に配置されます。背景の装飾的な要素をコンテンツの後ろに配置したいときに使います。ただし、スタッキングコンテキストを持つ祖先の背景よりは前面に留まるため、さらに奥に隠したい場合は、DOMの構造を変えるか isolation: isolate を使って調整します。

Q. opacity: 0 にした要素もスタッキングコンテキストを作りますか?

はい、作ります。opacity: 0 は視覚的には見えませんが、CSSの仕様上は「opacity が 1 未満の要素」に該当するため、スタッキングコンテキストを生成します。また、opacity: 0 にしただけではポインターイベントも受け付け続けます。完全に非表示にしたい場合は visibility: hidden や display: none との組み合わせを検討してください。

Q. Reactなどのコンポーネント設計でz-indexを管理する良い方法は?

Reactなどでは、モーダル・ドロップダウン・トーストなどのUI要素をReact Portal(ReactDOM.createPortal)を使ってbody直下にレンダリングするパターンが標準的です。これにより、コンポーネントがどのネスト階層に書かれていても、DOM上はbodyの直接の子として配置されるため、スタッキングコンテキストの問題を回避できます。z-indexの値はCSS変数またはデザイントークンで管理し、スタイルシートに集約します。

Q. z-indexはSEOに影響しますか?

直接的なSEOへの影響はありません。ただし、z-indexの誤設定でコンテンツが視覚的に隠れてしまっている場合、Googleのレンダリングエンジン(Chromiumベース)がそのコンテンツをどう評価するかは非自明です。重要なコンテンツがCSSで不可視になっている状態は避けるべきです。また、モーダルや固定ヘッダーがCore Web Vitals(特にCLS・INP)に影響するケースもあるため、パフォーマンス面での配慮も必要です。

まとめ

z-indexは単純に見えて、スタッキングコンテキストという概念を理解しないと思い通りに動かせないプロパティです。「数値を大きくすれば前に来る」という単純なルールは、スタッキングコンテキストの境界を越えた途端に崩れます。

  • z-indexはposition: relative / absolute / fixed / sticky のいずれかとセットで使う
  • スタッキングコンテキストは「トレイ」のようなもの。内部の重なり競争はトレイをまたがない
  • opacity / transform / filter / will-change は意図せずスタッキングコンテキストを生成する
  • position: fixed の要素の祖先にtransformがあるとfixedの挙動が壊れる
  • モーダルはbody直下に配置するか、<dialog> 要素を使うのがベストプラクティス
  • z-indexの値はCSS変数(–z-modal: 400 など)でまとめて管理する
  • 問題が起きたらブラウザDevToolsで親要素を遡り、スタッキングコンテキストの原因を特定する

関連記事

あわせて読みたい:CSSだけでモーダル(ポップアップ)を作る方法

WithCodeを体験できる初級コース公開中!

WithCodeを体験できる初級コース公開中!

初級コース(¥49,800)が完全無料に!

  • 期間:1週間
  • 学習内容:
    ロードマップ/基礎知識/環境構築/HTML/CSS/LP・ポートフォリオ作成
    正しい学習方法で「確かな成長」を実感できるカリキュラム。

副業・フリーランスが主流になっている今こそ、自らのスキルで稼げる人材を目指してみませんか?

未経験でも心配することはありません。初級コースを受講される方の大多数はプログラミング未経験です。まずは無料カウンセリングで、悩みや不安をお聞かせください!

この記事を書いた人

WithCodeでWeb制作を習得後、フリーランスエンジニアとして活動。HTML/CSS・JavaScript・WordPress案件を中心に年間20件以上の制作実績を持つ。「難しい技術をわかりやすく」をモットーに、初心者〜中級者向けの技術記事を執筆。副業・フリーランス独立を目指す方に向けた情報発信に注力している。

– service –WithGroupの運営サービス

  • WithCode
    - ウィズコード -

    スクール

    「未経験」から
    現場で通用する
    スキルを身に付けよう!

    詳細はこちら
  • WithFree
    - ウィズフリ -

    実案件サポート

    制作会社のサポート下で
    実務経験を積んでいこう!

    詳細はこちら

公式サイト より
今すぐ
無料カウンセリング
予約!

目次