z-index の役割
要素が重なったとき、z-index がどれを手前に描くかを決めます。数値が大きいほうが勝ちます。位置指定された要素 (static 以外の position) と、flex と grid のアイテムで効きます:
z-index がなければ、HTML で後に書いた要素が前の要素の上に描かれるので、C が B を覆い、B が A を覆います。z-index の値でこの順番が逆になり、A が一番手前、次に C、その後ろに B が来ます。
値
| 値 | 意味 |
|---|---|
auto (初期値) | 兄弟要素と同じレベル 0 に重ねられ、スタッキングコンテキストを作らない |
0 | レベル 0 だが、スタッキングコンテキストを作る |
| 正の整数 | 小さい値より手前 |
| 負の整数 | レベル 0 より奥。親がスタッキングコンテキストを作っていれば、親の内容よりも奥 |
有効なのは整数だけです。z-index: 1.5 は無視されます。
z-index 9999 でも負ける理由: スタッキングコンテキスト
z-index の値は、ページ全体で比べられるわけではありません。要素は スタッキングコンテキスト にまとめられ、z-index は同じコンテキストの中の要素の順番だけを決めます。コンテキスト自体は、それを作った要素の z-index を使って、1 つのレイヤーとして配置されます。
下の例では、青いドロップダウンは z-index: 9999 ですが、z-index: 1 のヘッダーの中にあります。そのヘッダーの兄弟である緑のバナーは z-index: 2 です。ドロップダウンを含むヘッダーのレイヤー全体が、バナーより奥にあります:
ボタンを押してください。z-index: auto にするとヘッダーはスタッキングコンテキストを作らなくなるので、ドロップダウンの 9999 とバナーの 2 が直接比べられ、ドロップダウンが手前に出ます。ドロップダウンを 99999 に上げても解決しなかったはずです。
スタッキングコンテキストを作るもの
次のどれかがあると、要素はそれ自身のスタッキングコンテキストになり、内側のすべての z-index を閉じ込めます:
| プロパティ | 条件 |
|---|---|
position: relative または absolute | z-index が auto 以外 |
position: fixed または sticky | 常に |
| flex または grid のアイテム | z-index が auto 以外 |
opacity | 1 未満 |
transform、scale、rotate、translate | none 以外の値 |
filter、backdrop-filter | none 以外の値 |
isolation | isolate |
mix-blend-mode | normal 以外の値 |
will-change | 上のどれかのプロパティを指定したとき |
contain | layout または paint (と strict、content) |
意外なのは opacity と transform です。親にフェードやホバーのアニメーションを追加すると、突然そのドロップダウンが次のセクションの奥に隠れることがあります。
ツールチップの z-index: 10 なら、隣の列より手前に出るはずです。ところが opacity: 0.99 によって 1 列目は z-index auto (レベル 0) のスタッキングコンテキストになり、2 列目は同じレベルで HTML の後にあるので、ツールチップを含む 1 列目全体の上に描かれます。opacity の行を消すと、ツールチップはまた手前に出ます。
負の z-index
負の z-index は要素を兄弟要素の奥に置きます。親がスタッキングコンテキストを作っていないと、親の背景より奥に行ってしまうこともあり、よく驚かれます。親に isolation: isolate を指定すると、見た目に何の影響もなくコンテキストが作られ、負の値の子要素がその中にとどまります:
isolation がないと、琥珀色の枠はボタン自身の灰色の背景の奥に落ち、端だけが見えます。isolation: isolate があれば、枠はボタンのスタッキングコンテキストの中、つまり背景より手前、文字より奥にとどまります。
z-index の目盛りを決める
数値をどんどん大きくする代わりに、サイト全体で名前付きのレイヤーをいくつか決めておきましょう:
:root {
--z-dropdown: 10;
--z-sticky: 20;
--z-overlay: 30;
--z-modal: 40;
--z-toast: 50;
}
.modal { position: fixed; z-index: var(--z-modal); }
重なりのバグのほとんどは、数値ではなくスタッキングコンテキストのバグです。小さな目盛りと、内部で z-index を使うコンポーネントへの isolation: isolate で、かなり防げます。z-index を効かせるには位置指定が必要です。position のページ で解説しています。
よくある間違い
- static の要素に z-index を付ける。
position: relativeを追加します (または flex や grid のアイテムにします)。 - 9999 まで上げていく。 親が競合相手より奥のスタッキングコンテキストを作っていれば、子要素の値では勝てません。代わりにコンテキストを探しましょう。
- 親に新しく
opacity、transform、filterを付けて、 ドロップダウンを突然閉じ込めてしまう。 - 小数の値。
z-index: 1.5は無効です。 z-index: 0とautoが同じだと思う。0はスタッキングコンテキストを作り、autoは作りません。- 深くネストしたコンポーネントの中のモーダル。
<body>の終わり近くに描画する (または<dialog>要素のトップレイヤーを使う) と、祖先のコンテキストに閉じ込められません。
よくある質問
z-index は何をしますか?
重なった要素の重なり順を決めます。同じスタッキングコンテキストの中では、値が大きいほうが小さいほうの手前に描かれます。初期値は auto で、値は負の数を含む整数です。
z-index が効かないのはなぜですか?
要素が位置指定されていない (z-index には static 以外の position か、flex や grid の子要素であることが必要) か、より低い順番のスタッキングコンテキストを作る親の中にあるかのどちらかです。子要素は、自分の z-index がいくつでも、自分のスタッキングコンテキストより手前にある要素の上には出られません。
スタッキングコンテキストとは何ですか?
1 つのレイヤーとしてまとめて重ねられる要素のグループです。その中では子要素が z-index の順に並び、外側からは、グループ全体がそれを作った要素の z-index で配置されます。
スタッキングコンテキストを作るものは何ですか?
主なものは、ルート要素、z-index が auto 以外の位置指定された要素、position: fixed か sticky、z-index を持つ flex や grid の子要素、1 未満の opacity、transform、filter、isolation: isolate、それらのプロパティを指定した will-change です。
position なしで z-index は効きますか?
普通の block や inline の要素では効きません。position: static は z-index を無視します。flex アイテムと grid アイテムでは、position なしで効きます。