Dennis Schubert

WebCompat Tale: CSS Flexbox and the order of things

2021-06-08 mozilla, webcompat

Have you thought about the order of things recently? Purely from a web development perspective, I mean.

The chances are that you, just like me, usually don’t spend too much time thinking about the drawing order of elements on your site when writing HTML and CSS. And that’s generally fine because things usually just feel right. Consider the following little example:

Source:

<style>
  #order-demo-one {
    position: relative;
    height: 100px;
    width: 100px;
  }

  #order-demo-one .box {
    position: absolute;
    bottom: 0;
    left: 0;
    right: 0;
    top: 0;
  }

  #order-demo-one .first {
    background-color: blue;
  }

  #order-demo-one .second {
    background-color: red;
  }
</style>
<section id="order-demo-one">
  <div class="box first"></div>
  <div class="box second"></div>
</section>

Result:

You could probably tell, without even looking at the result, that the second box - the red one - should be “on top”, completely covering up the blue box. After all, both boxes have the same size and the same position, but since the second box is placed after the first box, it’s drawn on top of the first one. To me, this feels pretty intuitive.

Let’s add some Flex to it

Now, let’s make things a bit more complicated. If you’re reading this article, I hope you’re at least slightly familiar with CSS Flexbox. And as you might know, flex-items have an order property, which you can use to reorder the items inside a flex container. Here’s the same example as before, but this time inside a flexbox container, with the items reordered. Note that this demo uses the same source as above, but I’m only showing relevant changes here.

Source:

<style>
  #order-demo-two {
    display: flex;
  }

  #order-demo-two .first {
    order: 2;
  }

  #order-demo-two .second {
    order: 1;
  }
</style>
<section id="order-demo-two">
  <div class="box first"></div>
  <div class="box second"></div>
</section>

Result:

Okay, now we used order to swap positions of the first and second boxes. And as you can see in the demo … nothing changed. What? This is where things start becoming a bit counter-intuitive because this test case is actually a bit of a trick question.

Here is what the CSS Flexbox spec says about the order property:

  1. A flex container lays out its content in order-modified document order, starting from the lowest numbered ordinal group and going up. Items with the same ordinal group are laid out in the order they appear in the source document.
  2. This also affects the painting order, exactly as if the flex items were reordered in the source document.
  3. Absolutely-positioned children of a flex container are treated as having order: 0 for the purpose of determining their painting order relative to flex items.

(List points added by me; the original is a single block of text.)

Point 1 is what we intuitively know. An element with order: 2 is shown after order: 1. So far, so good. Point 2, however, says that if you specify order, the elements should behave as if they have been reordered in the HTML. For our example above, this should mean that both of these HTML snippets should behave the same:

<section id="order-demo-two">
  <div class="box first"></div>
  <div class="box second"></div>
</section>
<section id="order-demo-two">
  <div class="box second"></div>
  <div class="box first"></div>
</section>

But we can clearly see that that’s not how it works. That is because in the spec text above, point 3 says that if a flex item is absolutely-positioned, it is always treated as having order: 0, so what we define in our CSS doesn’t actually matter.

Flex order, for real this time.

So instead of having the absolutely positioned element as the flex item, let’s build another demo that has the absolute element inside the flex item.

Source:

<style>
  #order-demo-three {
    display: flex;
    position: relative;
    height: 100px;
    width: 100px;
  }

  #order-demo-three .first {
    order: 2;
  }

  #order-demo-three .second {
    order: 1;
  }

  #order-demo-three .inner {
    position: absolute;
    bottom: 0;
    left: 0;
    right: 0;
    top: 0;
  }

  #order-demo-three .first .inner {
    background-color: blue;
  }

  #order-demo-three .second .inner {
    background-color: red;
  }
</style>
<section id="order-demo-three">
  <div class="box first">
    <div class="inner"></div>
  </div>
  <div class="box second">
    <div class="inner"></div>
  </div>
</section>

Result:

And now, I might have lost you. Because, as of the time of writing this, what you see as the result depends on which browser you read this blog post in. In Firefox, you’ll see the blue box on top; but pretty much everywhere else, the red box will still be on top.

The question now is: who is right? And instead of just telling you the answer, let’s work it out together. Rule 3 from above does not apply here: The flex items are not absolutely positioned, so the order should be taken into consideration. To check if that’s the case, we can look at rule 2: the code should behave the same if we reorder the elements in the HTML. We can build a test for that:

Source:

<section id="order-demo-four">
  <div class="box second">
    <div class="inner"></div>
  </div>
  <div class="box first">
    <div class="inner"></div>
  </div>
</section>

Result:

(again, the code is the same as the one in #order-demo-three, but I’m just showing the HTML to keep it easier to read)

If you’re reading this in Firefox, then the last two test cases behave the same: they’ll show the blue box. However, if you’re in Chrome, Safari, or Edge, there will be a difference: the first case will show the red box, the second case shows the blue box. If you now think that this is a bug in Blink and WebKit: you are right, and that bug has been known for a while.

Uh, what now?

This might sound like a super weird edge-case, and that’s probably right. It is a weird edge case. But unfortunately, as with pretty much all things that end up on my desk, we discovered this edge-case by investigating real-world breakage. Here, we received a report about the flight date picker on flydubai.com being broken, where in Firefox, there is an advertising banner on top of the picker. That’s caused by what I described here.

The Blink issue I linked earlier was opened in 2016, and there hasn’t been much progress on there since. I’m not saying this to blame Google folks; that’s just highlighting that changing things like this is a bit tricky sometimes. While there appears to be a consensus that Firefox is right, changing Chrome to match Firefox could result in an undefined number of broken sites. So you have to be careful when pushing such a change.

For now, I decided to go ahead and add a web-platform-test for this, because there is none yet. Currently, there’s also a cross-browser compat effort, “Compat2021”, going on, and CSS Flexbox is one of the key areas everyone wants to work on to make it a bit less of a pain for web developers. Maybe we can get some progress done on this issue as well. I will certainly try!

And with that, I have to end this post. There is no happy end, there isn’t even a certainty on what - if anything - will happen next. Sometimes, that’s the nature of our work. And I think that’s worth sharing, too.