概要
ポップアップが隠れる。モーダルが出ない。トーストが後ろに回る。
フロントエンド開発において、z-indexによるレイヤー衝突は、最もよくあるUIバグの一つだ。
そしてその大半は、「z-indexの数値が足りない」のではなく、z-indexの設計思想が欠如していることに起因する。
この記事では、z-indexの仕様と誤解されがちなポイントを明確にし、再現性と秩序あるレイヤー設計を実現するための実践ノウハウを共有する。
対象環境
CSS2 / CSS3 対応ブラウザ(全主要ブラウザ)
そもそもz-indexとは何か?
- 同一スタックコンテキスト内の要素の重なり順を制御するための数値
- 数値が大きい方が上に表示される
- 負の数も使用可能(下に潜る)
スタックコンテキスト(Stacking Context)の概念がすべてを決める
スタックコンテキストが作られる条件(代表例)
-
position
がrelative
,absolute
,fixed
,sticky
で、z-index
が指定されている -
z-index
がauto
の場合、その親のスタックコンテキストを継承 -
opacity
が 1 未満、filter
が指定されている要素もスタックを生成
図でイメージするなら:
[ルートスタック]
├── z-index: 1 の要素(通常要素)
├── z-index: 10 の要素(モーダル)
└── スタックコンテキストA(子要素)
└── z-index: 9999 でも、外に出られない ← ← ← ここが重要
→ スタックが違えば、いくら数値を上げても意味がない
よくある誤解とバグの温床
❌ モーダルが表示されない → z-index: 9999 にする → 直らない
→ 原因:別のスタックコンテキスト内で競合しているため
❌ トースト通知がヘッダーに隠れる → z-index: 10000
→ 解決したが、別のUIが今度は裏に
→ 原因:z-index値が場当たり的に上書きされ、全体のレイヤー構成が崩壊
解決策:z-indexの設計レイヤーを明文化する
推奨:グローバルレイヤー設計の導入
:root {
--z-base: 0;
--z-header: 10;
--z-dropdown: 100;
--z-modal: 1000;
--z-toast: 10000;
}
.header {
z-index: var(--z-header);
}
.modal {
z-index: var(--z-modal);
}
.toast {
z-index: var(--z-toast);
}
→ この設計により、意味ベースでレイヤー管理が可能に
→ 場当たりの数値指定から脱却し、再現性と可読性の高いCSSへ
応用:ローカルレイヤー設計も併用する
.card {
position: relative;
z-index: 1;
}
.card .hover-overlay {
z-index: 2;
}
→ グローバルとローカルで 「2階建てのレイヤー管理」 を行うことで、衝突を防ぎつつ柔軟な設計が可能
テクニック:スタックを切ることで意図的にレイヤー独立させる
.modal {
position: relative;
z-index: var(--z-modal);
isolation: isolate; /* スタックコンテキストを明示的に生成 */
}
→ isolation: isolate
は z-index
の衝突を防ぐためのモダンな手段。
→ 高度なUI制御ではこのアプローチが有効。
よく使われるz-index設計ガイドライン(例)
意味 | 変数名 | 値 |
---|---|---|
ベース | --z-base |
0 |
固定ヘッダー | --z-header |
10 |
ドロップダウン | --z-dropdown |
100 |
モーダル | --z-modal |
1000 |
トースト通知 | --z-toast |
10000 |
デバッグUI | --z-dev |
99999 |
→ このように意味と構造で数値を管理することで、衝突は予防できる
結語
z-indexは、CSSの中でも最も誤解され、最もバグを引き起こすプロパティの一つだ。
しかしその本質は「数字の大小」ではなく、スタックコンテキストという設計構造にある。
設計しない限り、z-indexは破綻する。
逆に、意味を持たせて管理すれば、どんな複雑なUIも秩序あるものにできる。
レイヤーとは、視覚効果ではなく設計そのもの。
UIの奥行きを制するには、z-indexという構造を理解することから始まる。