DEV Community

Salil Naik
Salil Naik

Posted on

The uncanny relationship between position fixed and transform property.

TL;DR

An element with position:fixed is positioned relative to the document (the viewport) which acts as its containing block.

But, it will NOT always be relative to the document. When any element has transform, filter or perspective property, it acts as a containing block for all its descendants, including the elements whose position is set to fixed.

Now, the long story...

While I was working on enhancing the navigation bar of the
Polygon Wallet, I found myself stuck with a strange error. I spent almost a day trying to figure out the issue, consulted with the senior frontend engineers and also thought of making a separate Vue component as a workaround.

When I finally found the bug, I wouldn't shy away to say that my ego went crashing down, as it was a CSS bug, and my CSS skills are something I take pride in.

I was doing some changes to the code to align the dropdown menus to the cente of the navigation item (as shown in the picture below) for which I had to move the dropdown component inside the <li>. Doing this would allow me to use the CSS position and transform properties to align the dropdown exactly at the bottom-center of the navigation item.

Dropdown menu opens at the center

Till now, everything was working fine and nothing was unusual and unexpected.

Inside the dropdown menu, there were a bunch of notification items, which when clicked would open the modal component.

The components were nested something like this



<li>
  <span>Notifications</span>

  <dropdown-menu>
    <item>
      <modal />
    </item>

    <item>
      <modal />
    </item>
  </dropdown-menu>
</li>


Enter fullscreen mode Exit fullscreen mode

The modal component, which was supposed to open at the center of the screen when clicked on the item, would just behave weirdly and open inside the dropdown menu itself. I was sure that, any element with a fixed position would ignore all its ancestors and position itself relative to the document (the viewport or the root element). But that just didn't happen. The ancestor element, in this case, the dropdown menu was acting as a containing block for the modal component.

Modal opens in a weird way

And as you might have guessed, it was due to the transform property. Any element with transform, filter or perspective property will act as a containing block to all its descendants, including the elements whose position is set to fixed. Hence the modal, instead of positioning itself with respect to the document, positioned itself relative to the dropdown-menu component where I had used the transform property to align it exactly at the bottom-center of the navigation item. See CSS Transform Spec

How did I fix it?

Well, there is no fix for that, I just removed the transform property, gave a fixed width to all the dropdown components and achieved the same result with the help of the margin and position properties. But this uncanny relationship between position and transform property reminded me why CSS takes the crown as my favorite language even when I have a multi-paradigm language like JavaScript in my stack. ❀

Modal opens as expected

Thank you!

If you want to learn about CSS positions, refer to
CSS Tricks
MDN Web Docs

Top comments (8)

Collapse
 
shridhar_kdev profile image
Shridhar Kamat

Really helpful! πŸ˜€

Collapse
 
salilnaik profile image
Salil Naik

I'm glad, Shridhar.

Collapse
 
peresnegro profile image
Porkopek

If you work with React, you can use a Portal

Collapse
 
codingcat profile image
Coding Cat

Thank you! I can confirm for anyone else who finds this article while experiencing this CSS bug that React Portals solve the issue. The current doc for it is react.dev/reference/react-dom/crea.... Very easy to implement if you are already up to speed with React in general, and it solves the bug.

Collapse
 
arbs23 profile image
Afzalur Rahman Sabbir

@peresnegro vue already have Teleport

Collapse
 
codingcat profile image
Coding Cat

Thank you so much for this article, @salilnaik! I spent about 15 minutes dealing with this bug and was just starting to enter the beginning stages of ripping my hair out over it and had just deduced that the transform property being set on the parent element was causing the issue, but thankfully I thought to search "element position fixed within element position absolute transform translate" and your post was on the first page of DuckDuckGo, and it confirmed what I was experiencing.

Unfortunately, in my case, using something other than transform: translate(y,x) on the parent element was not a possibility, but in the comments section, @porkopek mentioned using React Portals to work around the issue, which solved the problem for me!

And, based on the another commenter, it looks like there is also a similar tool in the Vue framework. I don't know Vue so can't confirm/deny on that one.

You might consider updating your article to reflect the two possible alternate solutions to the problem using React or Vue, as some people might not make it all the way down to the comments section in search of a solution.

Anyway, thanks again for this post! :)

Collapse
 
arbs23 profile image
Afzalur Rahman Sabbir

Nice explanation! πŸ’–

Collapse
 
gboladetrue profile image
Ibukunoluwa Popoola

Very useful! Faced the same issue with a modal rendered in a list