疑似要素を使ってデザインを再現する際、初心者の方なら思い通りにならないことも多いのではないでしょうか?
例えば画像に影のようなあしらいをつけたいのに、「どうしても画像の上に来てしまう。z-indexの重なりがうまくいかない!」など。
私自身もこの問題に繰り返し直面し、デベロッパーツールにて何度もz-indexの値を上下させてその場しのぎでやってきた人間です。
この経験をもとに、なぜこのような現象が発生するのか、はっきりと原因を認識して今後どのように対処すればよいのかを解説します!
疑似要素のz-indexが効かない原因
実際にコードでみてみよう!

結論から言うと疑似要素の元要素にz-indexを付与しているからです。これを実際に確認して理解を深めていきましょう。
次の例でコードを確認してみましょう!
See the Pen 疑似要素のz-indexのきかせ方 by UCHINO (@UCHINO-web-service) on CodePen.
上のコードでは赤のbox1をhtmlで作成して青(before)と緑(after)のboxは疑似要素で作成してます。
そしてz-indexを使用してbox1の下に疑似要素beforeを、疑似要素beforeの下に疑似要素afterを重ねています。
この状態を実現するために、疑似要素beforeと疑似要素afterにそれぞれz-index-2と-3を付与しています。
次にうまくいかなかったときはどうだったか説明します。
上のコードで右下にあるRun Penボタンを押してこれを少しイジってみましょう!
CSSを表示させて.box1のCSSにコメントオフしてあるindex: -1;を
コメントオフを削除してください。
するとbox1が疑似要素beforeの下に潜り込んだ状態になったと思います。(コードを触ってない人は下の画像を参照)

この状態になったとき当時の私はこんな風に考えていました。

box1::beforeとbox1::afterにはそれぞれz-index -2と -3を設定している。そしてbox1にはz-index -1を与えている。



疑似要素(box1::beforeとbox1::after)のz-indexマイナス値が大きいはずなのにbox1の下にならないのはなぜ?
このときは本当に混乱していました。
次に別の設定画像を見てください。下の画像ではz-indexをまったく効かせていない状態です。


z-indexがない場合は、コードに書いてある順番が下の要素ほど上に重なります。疑似要素beforeとafterならafterが上になります。
それでは今の基本を踏まえて、もう一度コードを確認しましょう!
See the Pen 疑似要素のz-indexのきかせ方 by UCHINO (@UCHINO-web-service) on CodePen.
上のコードでのポイントは
- 疑似要素の元要素(今回の例でbox1)にz-indexを与えないこと。
- 元要素の親要素(box-wrapper)に重なりの基準としてのz-indexを与える。
ポイント①
「元要素にz-indexを与えないこと」はこの記事の冒頭で体験してもらった部分です。z-indexを与えないことによって親要素のz-indexを基準値に出来ます。
ポイント②
疑似要素のz-indexを考えるときはbox1(疑似要素の元要素)の親要素(box-wrapper)に対してマイナスの値を与えるとbox1の下に配置出来ます。
これでもうまくいかない方へ
正直な話、私はここまでの理解ですすめていましたが、うまくいくときとうまくいかないときもありました。
階層さえ同じならz-indexは効くと思い込んでいました。
実際、コンテンツが複雑すぎて同階層だと認識していたのに実は子要素だったとか、relativeが効いている要素の外の要素にz-indexの数字を上げたり下げたりしていたこともあります。
このときは深く追求しませんでした。これはパターンがちがうのかなくらいで思考を止めていたのです。
しかし私がズレていたのは、要素の階層ではなくスタッキングコンテキストのレベル(階層)でした。
スタッキングコンテキストとは
簡単なイメージとしては重なりあうグループです。
何をもってスタッキングコンテキストとなるのかというと、
position(relative, absolute, fixed, sticky)とz-indexの組み合わせ
position が relative, absolute, fixed, sticky のいずれかで、かつ z-index が設定されている場合。
opacity
opacity が 1 未満(例えば opacity: 0.5;)の場合。
transform
transform プロパティ(例えば transform: translate(10px, 10px);)が設定されている場合。
以上がChat-GPTの回答で代表的なものを抜粋したものです。今回関係するのはpositionですね。
z-indexと疑似要素で私がハマった思考
See the Pen 疑似要素のz-indexのきかせ方 by UCHINO (@UCHINO-web-service) on CodePen.
もう一度コードに触れて確認してみましょう。
最初に.box-wrapperにz-indexを付与せず、.box1にだけz-indexを与えて::beforeと::afterの重なり順を制御しようとしていました。
しかしこの状態では.box1にいくら高い数字をいれても::beforeが.box1の下になることはありません。
それは擬似要素の描画順序(::before → 親要素の内容 → ::after)は、z-index の値に関係なく適用されるため、.box1 の内容が ::before よりも後に描画されます。
スタッキングコンテキストの考え方でいうと.box1とその疑似要素は同じスタッキングレベルではないということです。
.box1と疑似要素を重なり順で操作するなら.box-wrapperにposition relativeとz-indexを指定し、.box1とその疑似要素のスタッキングレベルを合わせると思うように操作できます。
ここまでの説明をすんなり理解出来る方は少ないと思います。
私もそうでしたが、.box1とその疑似要素は同じレベルにあるのでx-indexの指定が効かないことが理解できませんでした。
当時は詰まるたびに見返すことで少しずつ理解が進んでいくイメージでした。
まとめ
疑似要素のz-indexは思わぬトラブルになりがちですが、適切な方法で設定すれば、混乱を避けることができます。
今回のポイントは、簡単にいうと元要素にz-indexを適用しないこと。元要素ではなく親要素にz-indexの基準値を設定する。
深く理解するならスタッキングコンテキストも合わせて生成AIなどに確認するのもいいと思います。
疑似要素のz-indexでハマったらまたお会いしましよう!