386

I am trying to style a element with the :after pseudo element CSS selector

#element {
    position: relative;
    z-index: 1;
}

#element::after {
    position:relative;
    z-index: 0;
    content: " ";
    position: absolute;
    width: 100px;
    height: 100px;
}

It seems like the ::after element can not be lower then the element itself.

Is there a way to have the pseudo element lower then the element itself?

adardesign
  • 33,973
  • 15
  • 62
  • 84
  • 2
    Related: (same reason and solution) - [How to get a parent element to appear above child](https://stackoverflow.com/questions/1806421/how-to-get-a-parent-element-to-appear-above-child) and [How to make child element higher z-index than parent?](https://stackoverflow.com/questions/16057361/how-to-make-child-element-higher-z-index-than-parent) – TylerH Apr 22 '19 at 14:33
  • Why not use `:before` ? – Robert Jul 09 '22 at 15:11

9 Answers9

472

Pseudo-elements are treated as descendants of their associated element. To position a pseudo-element below its parent, you have to create a new stacking context to change the default stacking order.
Positioning the pseudo-element (absolute) and assigning a z-index value other than “auto” creates the new stacking context.

#element { 
    position: relative;  /* optional */
    width: 100px;
    height: 100px;
    background-color: blue;
}

#element::after {
    content: "";
    width: 150px;
    height: 150px;
    background-color: red;

    /* create a new stacking context */
    position: absolute;
    z-index: -1;  /* to be below the parent element */
}
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Position a pseudo-element below its parent</title>
</head>
<body>
  <div id="element">
  </div>
</body>
</html>
Arley
  • 947
  • 1
  • 13
  • 19
  • 162
    However, if you have a normal parent div with a background, the lower element will be hidden. In this case, give the parent relative positioning and a z-index of 1. See: http://jsbin.com/juputice/3/edit – Stephen Saucier Jul 10 '14 at 14:32
  • 6
    Only one comment to this code: if parent div (parent of owner of pseudo element) has a background (say body tag) it will overlap pseudo element background. To fix it parent div must be set to relative positioning and z-index value lower than pseudo element – Alex Shwarc Apr 08 '16 at 19:20
  • 39
    Also to note, some weird behaviour, if you use transform on the parent element the background of the parent element will still be underneath the pseudo element whilst the content will be on top. – Tom C Nov 28 '16 at 12:06
  • 9
    Thanks @spsaucier - seems it's really specific about the numbering, i.e. needs to be 1 and -1, any other values don't seem to work in this context. – Nathan Hornby Feb 20 '17 at 18:18
  • 2
    @NathanHornby I have similar findings like yourself: upper div should be a positive z-index and the lower layer (pseudo-element) *must* be a negative (eg. `-1`) – Dimitry K Aug 02 '17 at 11:49
  • 2
    Why doesn't my example work? https://codepen.io/anon/pen/wqNOjd ... only works with z-index:auto; on parent – mjs Sep 02 '17 at 13:22
  • 5
    I have tried this with parent position: fixed and z-index: 1 and the pseudo element as in the above code. Does not work in Chrome. – Ben Mar 10 '18 at 14:45
  • 1
    This doesn't work in Chrome. – Aero Wang Mar 17 '19 at 14:08
  • Why is this the accepted answer? It doesn't work - the code snippet doesn't use psuedo element, which was the OP's quesiton... – Jillian Hoenig Jul 08 '22 at 20:03
301

I know this is an old thread, but I feel the need to post the proper answer. The actual answer to this question is that you need to create a new stacking context on the parent of the element with the pseudo element (and you actually have to give it a z-index, not just a position).

Like this:

#parent {
    position: relative;
    z-index: 1;
}
#pseudo-parent {
    position: absolute;
    /* no z-index allowed */
}
#pseudo-parent:after {
    position: absolute;
    top:0;
    z-index: -1;
}

#parent { position: relative; z-index: 1; }
#pseudo-parent { position: absolute; } /* no z-index required */
#pseudo-parent:after { position: absolute; z-index: -1; }

/* Example styling to illustrate */
#pseudo-parent { background: #d1d1d1; }
#pseudo-parent:after { margin-left: -3px; content: "M" }
<div id="parent">
 <div id="pseudo-parent">
   &nbsp;
 </div>
</div>
temporary_user_name
  • 35,956
  • 47
  • 141
  • 220
Adam Jenkins
  • 51,445
  • 11
  • 72
  • 100
  • 6
    I am not sure what you mean by `#parent` and `#pseudo-parent`, do you mind to show me a jsFiddle? or any example, thanks anyway. – adardesign Jan 31 '12 at 14:49
  • 2
    @adardesign - jsfiddle is read only at the moment, the HTML is available at pastebin - http://pastebin.com/cjYT2aWW – Adam Jenkins Feb 09 '12 at 16:58
  • 5
    This is a more complete answer, Adam. Thanks! I'm finding (on Chrome) that the z-index of #pseudo-parent is not only not required, but is also deleterious-- (whereas the z-index of #parent has no effect), and that the :after div will only appear under the parent when its z-index is negative. – JohnK Aug 26 '12 at 22:46
  • Nice! Also, it should be noted that you can set `position: relative` on `#pseudo-parent` and this will still work. – Gerbus Mar 27 '13 at 17:07
  • @arley - if you look at the answer history, the "accepted answer" was changed to clone mine. Also, my second comment points to a paste bin that illustrates the proper HTML/CSS to use. – Adam Jenkins May 24 '14 at 11:16
  • @arley: Not sure what your point is. 1) You're asking someone to explain their answer to the community, when yours is the one in need of explanation - something even the comments above yours aren't any less eager to point out 2) re: changing the accepted answer - [isn't that what you've been doing over the last month?](http://stackoverflow.com/posts/10822168/revisions) Oh and don't worry, plagiarism isn't something you can sue someone for. – BoltClock May 25 '14 at 15:35
  • 2
    @arley: Your answer came several months after this one, and it wasn't even an answer at the time - it was a comment saying it doesn't work. – BoltClock May 26 '14 at 01:18
  • This fixed a stacking problem I was having with a timeline (where it was .timeline.item:before trying to stack behind .timeline.item and I hadn't messed with .timeline's z-index), thank you for posting this. – Event_Horizon Jul 15 '15 at 18:03
  • cannot we use css to scope parent element? No? Bug buggy me .. How to avoid negative z-index? – Milche Patern Oct 16 '15 at 04:06
  • @MilchePatern - why are you trying to avoid a negative z-index? The CSS spec calls for the z-index to be an integer, it does not say positive or unsigned integer. http://www.w3.org/TR/CSS21/visuren.html#z-index – Adam Jenkins Oct 16 '15 at 11:20
  • @Adam Browser behaviors and specs are parts of a rendering engine spaghetti. – Milche Patern Oct 16 '15 at 21:38
  • I don't understand. This doesn't solve the problem. The question is asking, how to make a stacking sequence of parent -> pseudo element -> pseudo parent. This doesn't help – Boyang Sep 19 '16 at 13:54
  • 2
    @Boyang - seems like you're one of the very few that doesn't get that this is the solution the OP requested, thanks for the downvote. – Adam Jenkins Sep 19 '16 at 16:35
  • Thank you for this answer, it solved my similar stacking problem as well by learning that #pseudo-parent z-index is stronger than :before z-index. Do you know if there is some way how to apply concrete z-indexes to both #pseudo-parent (9990) and #pseudo-parent:before (9992) so I could stack something else from other #element (9991) between them? I have it as a more complex stacking structure with several levels that I need to control. – martinh_kentico Nov 10 '16 at 11:48
  • 1
    @martinh_kentico - you can't stack anything from another element between a parent and it's child if the parent has it's own stacking context. Keep in mind that pseudo elements are no different than child elements. Restructure your HTML to accomplish what you want. – Adam Jenkins Nov 10 '16 at 12:22
  • 1
    This worked for me! I'm happy i kept reading past the accepted answer. I usually dont but the other one didnt' work for me and this had so many upvotes. Thanks for pointing this out. – Diane May 08 '19 at 16:01
  • I applied this solution in here https://codepen.io/tiagosalema/pen/dyoQPPK?editors=1100, but it doesn't seem to be working. What am I missing? – Tiago Mar 25 '20 at 16:07
230

Try it out

el {
  transform-style: preserve-3d;
}
el:after {
  transform: translateZ(-1px);
}
Eugen Govorun
  • 3,006
  • 1
  • 11
  • 12
  • 25
    Thank you @Eugen. This was actually the only answer for me that worked. I had to set a background color in a pseudo element, both the element and the pseudo element were absolutely positioned. Works perfect. – Stef Van Looveren Jul 31 '18 at 15:06
  • 7
    This better answer; cover most cases specially when main div(element) have some transform. – Mohammad Shraim Oct 02 '18 at 16:17
  • 2
    In my case I needed to add `position: absolute` to `el:after`; otherwise, it works! – T04435 Apr 10 '19 at 06:03
  • 2
    While this is the only thing that worked for me on most browsers, it does not work in IE11 – Xavier C. Apr 24 '19 at 14:11
  • This worked for me on a complicated design - backround gradient on the background's pseudo element and semi-transparent image as main background, both stack one on the other. THanks. – Chris Osiak Apr 08 '22 at 16:18
  • It might look like a hack, but it's actually a pretty nice solution for some use cases, thank you a lot for coming up with this idea! :-D – Matthew Husák Aug 03 '22 at 19:31
  • Works great on Firefox and Chrome but I can't get this to work on Safari MacOS 12.6. The "child" element stays on top. – danielv May 11 '23 at 18:38
  • Works for Chrome on my Mac and also solves the background image problem the "accepted" solution still has. However, it won't work for Safari. – waldgeist Jun 28 '23 at 10:08
16

There are two issues are at play here:

  1. The CSS 2.1 specification states that "The :beforeand :after pseudo-elements elements interact with other boxes, such as run-in boxes, as if they were real elements inserted just inside their associated element." Given the way z-indexes are implemented in most browsers, it's pretty difficult (read, I don't know of a way) to move content lower than the z-index of their parent element in the DOM that works in all browsers.

  2. Number 1 above does not necessarily mean it's impossible, but the second impediment to it is actually worse: Ultimately it's a matter of browser support. Firefox didn't support positioning of generated content at all until FF3.6. Who knows about browsers like IE. So even if you can find a hack to make it work in one browser, it's very likely it will only work in that browser.

The only thing I can think of that's going to work across browsers is to use javascript to insert the element rather than CSS. I know that's not a great solution, but the :before and :after pseudo-selectors just really don't look like they're gonna cut it here.

Jared Forth
  • 1,577
  • 6
  • 17
  • 32
Gabriel Hurley
  • 39,690
  • 13
  • 62
  • 88
12

Speaking with regard to the spec (http://www.w3.org/TR/CSS2/zindex.html), since a.someSelector is positioned it creates a new stacking context that its children can't break out of. Leave a.someSelector unpositioned and then child a.someSelector:after may be positioned in the same context as a.someSelector.

Jared Forth
  • 1,577
  • 6
  • 17
  • 32
cweider
  • 466
  • 2
  • 5
10

I know this question is ancient and has an accepted answer, but I found a better solution to the problem. I am posting it here so I don't create a duplicate question, and the solution is still available to others.

Switch the order of the elements. Use the :before pseudo-element for the content that should be underneath, and adjust margins to compensate. The margin cleanup can be messy, but the desired z-index will be preserved.

I've tested this with IE8 and FF3.6 successfully.

D.N.
  • 2,160
  • 18
  • 26
  • 1
    Use this method with extreme caution if the content if your :before has any meaningful text. Text placed in :before is not read by search engines, so this affect SEO. Screen readers also do not read :before or :after text, so this makes your page less accessible. – the3seashells Apr 01 '14 at 01:04
  • 1
    If you're already using :after, the second point you make has no impact here. – Dan Loewenherz Jul 15 '17 at 19:29
7

Set the z-index of the :before or :after pseudo element to -1 and give it a position that honors the z-index property (absolute, relative, or fixed). This works because the pseudo element's z-index is relative to its parent element, rather than <html>, which is the default for other elements. Which makes sense because they are child elements of <html>.

The problem I was having (that lead me to this question and the accepted answer above) was that I was trying to use a :after pseudo element to get fancy with a background to an element with z-index of 15, and even when set with a z-index of 14, it was still being rendered on top of its parent. This is because, in that stacking context, it's parent has a z-index of 0.

Hopefully that helps clarify a little what's going on.

tsymyn
  • 79
  • 1
  • 2
1

I fixed it very simple:

.parent {
  position: relative;
  z-index: 1;
}

.child {
  position: absolute;
  z-index: -1;
}

What this does is stack the parent at z-index: 1, which gives the child room to 'end up' at z-index: 0 since other dom elements 'exist' on z-index: 0. If we don't give the parent an z-index of 1 the child will end up below the other dom elements and thus will not be visible.

This also works for pseudo elements like :after

Wachem
  • 47
  • 1
-7

I don't know if someone will have the same issue with this. The selected answer is partially correct.

What you need to have is:

parent{
  z-index: 1;
}
child{
 position:relative;
 backgr
  • 3
    The `child` block in this answer appears to be incomplete. There is no closing `}` and I suspect that the `backgr` is not a complete word. – AdrianHHH Mar 20 '19 at 23:05