Nona Blog

The mysterious case of the disappearing shadow, or how to ensure your pseudo-elements display as expected.

I recently ran into what, at first blush, seemed peculiar behaviour with CSS. I’d created a Button component with a shadow, but the designs needed the shadow to be slightly smaller than the button component:

The button styles without the shadow are quite straight-forward:

<div class="container">
  <button class="button">Click Me</button>
</div>
.button {
  font: helvetica, sans-serif;
  height: 64px;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  border-radius: 32px;
  background-color: #118ab2;
  font-size: 24px;
  font-weight: 500;
  color: white;
  position: relative;
  border: none;
  width: 250px;
}

However, you can’t achieve that drop-shadow with a normal box-shadow, so I went with a pseudo-element positioned below the button and scaled down to create the correct effect:

.button::before {
  content: '';
  position: absolute;
  top: 66%;
  left: 0;
  right: 0;
  bottom: -6px;
  background: black;
  z-index: -1;
  border-radius: 32px;
  box-shadow: 0 0 36px 30px black;
  transform: scale(.7);
  opacity: .2;
}

Voila!

…but, the plot thickens

All was going well until I tried to implement the button where one of its parents had a background set – because of the negative z-index, the shadow totally disappeared!

.container {
  background :#06d6a0; // <== I'm new
  
  /* mostly for layout: */
  width: 30vw;
  margin: 25vh auto;
  height: 50vh;
  display:flex; 
  align-items: center;
  justify-content: center;
  border-radius: 12px;
}

The issue is that all these elements are on the same stacking context, and so the z-index: -1; is placing the shadow below the background.

The solution: Create a new stacking context.

You can create a new stacking context by doing a bunch of thins (read them all in the MDN article ), the only option that I’ve found to actually work easily with no side-effects is transform. Any transform should work, and as long as you keep the default values, it won’t mess with anything:

transform: translate(0,0); 
transform: scale(1);

When I add this to the .container, the shadow is back!!

.container {
  background: #06d6a0;
  transform: scale(1);
  
  /* mostly for layout: */
  width: 30vw;
  margin: 25vh auto;
  height: 50vh;
  display:flex; 
  align-items: center;
  justify-content: center;
  border-radius: 12px;
}

Wait, there’s more!

Great! We’ve fixed the issue – but we’re still left with a bit of a problem in so much as our solution for the disappearing shadow is in no way coupled to the shadow, but to the obscure cause of the issue which could be anywhere up the DOM tree.

So, a better fix would be to implement the fix on an element closely associated with the <button /> element.

Unfortunately, simply adding it to the element with the shadow doesn’t work — or, rather it DOES work, but in so doing it breaks the styling:

.button {
  ...
  transform: scale(1);
}

What’s happening here? As expected, we’re creating a new stacking context from the button element – however, this means that the pseudo-element (which comes after the button in the DOM), now sits on top of the button element in the stack and no amount of negative z-indices will change that.

Finally, a solution

So – in my estimation, the best way to fix this is to place an inner span inside the button element, placing all the button styles, as well as the pseudo-element drop-shadow on that, allowing the button element to have the fix.

<div class="container">
  <button class="button">
    <span class="inner">Click Me</span>
  </button>
</div>
.button {
  border: none;
  display:block;
  height: 64px;
  width: 250px;
  background: transparent;
  transform: scale(1);
}

.inner {
  font: helvetica, sans-serif;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  border-radius: 32px;
  background-color: #118ab2;
  font-size: 24px;
  font-weight: 500;
  color: white;
  position: relative;
  width: auto;
  height: 100%;
}

.inner::before {
  content: '';
  position: absolute;
  top: 66%;
  left: 0;
  right: 0;
  bottom: -6px;
  background: black;
  z-index: -1;
  border-radius: 32px;
  box-shadow: 0 0 36px 30px black;
  transform: scale(.7);
  opacity: .2;
}

Conclusion

In summary, z-index is a tricksy little thing, but when working with it, it’s probably best to ensure you create a new context wrapped as tightly around the effect you’re going for as possible.

The entire code for this is in a codepen here:

Nona helps funded businesses accelerate their software projects. If you’d like to soundboard your tech project or your development team, book a consultation with us and we can chat through it! 

Avatar

Cam Olivier

Senior UI Developer - Nona

Add comment