::before pseudo-element stacking order issue

The contents of the div, which include the two pseudo-elements and the p element, participate in the same stacking context (relative to the div). This is because, as you note, all three of them are statically-positioned; in other words, not positioned at all. (Yes, these elements do stack along the z-axis while in flow; you simply cannot manipulate their stack levels using z-index because they’re not positioned.)

Here’s a summary1 of the order in which the various parts are drawn, bold emphases where relevant to your question:

Each box belongs to one stacking context. Each positioned box in a given stacking context has an integer stack level, which is its position on the z-axis relative other stack levels within the same stacking context. Boxes with greater stack levels are always formatted in front of boxes with lower stack levels. Boxes may have negative stack levels. Boxes with the same stack level in a stacking context are stacked back-to-front according to document tree order.

Within each stacking context, the following layers are painted in back-to-front order:

  1. the background and borders of the element forming the stacking context.
  2. the child stacking contexts with negative stack levels (most negative first).
  3. the in-flow, non-inline-level, non-positioned descendants.
  4. the non-positioned floats.
  5. the in-flow, inline-level, non-positioned descendants, including inline tables and inline blocks.
  6. the child stacking contexts with stack level 0 and the positioned descendants with stack level 0.
  7. the child stacking contexts with positive stack levels (least positive first).

Since div::before is inserted before the content of the div, and div::after is inserted after it, when they display inline in a static position they’ll naturally follow this rule, even if sandwiching a block element (the ordering takes both block boxes and inline boxes into account).

Notice that, for obvious reasons, backgrounds are usually painted first, with the content overlaying them:

  1. The p element, as a block-level element, has a background that’s painted first (#3).

  2. The inline-level pseudo-elements are then drawn with their backgrounds over the p background (#5). In the formatting model, they are siblings of the p element, so they all participate in the stacking context of the div and not that of the p.

  3. The div::before pseudo-element (both its content and background) appears behind the p text because it comes before the p in the visual tree.

  4. The div::after pseudo-element (both its content and background) appears in front of the p text because it comes after the p in the visual tree.

As indicated in my comment, if you make the pseudo-elements display as blocks, the background of the div::before will hide behind that of the p element, but not the text; instead, the text of the div::before will be positioned between the background and the text of the p element. Also notice that the background of the div::after is painted in front of that of the p, while the content is painted frontmost. Again, this has to do with backgrounds being painted before, or behind, content in relation to the rules above.


1 A much more detailed description can be found in Appendix E of the spec.

Leave a Comment