29

We have two divs with content and a third div that is a background with absolute position.

Container is a flexbox.

All works fine in Chrome and Safari, but Firefox and IE11 factors in the absolute positioned div, and distributes space between divs like there are 3 divs in a row.

enter image description here

I've made jsfiddle example. Is there any way to fix this bug? https://jsfiddle.net/s18do03e/2/

div.container {
  display: flex;
  flex-direction: row;
  width: 100%;
  height: 300px;
  justify-content: space-between;
  width: 100%;
  outline: 1px solid;
}
div.c1 {
  background: #aaeecc;
  width: 100px;
  position: relative;
  z-index: 50;
  top: 20px;
  display: flex;
}
div.c2 {
  background: #cceeaa;
  width: 200px;
  position: relative;
  z-index: 50;
  top: 20px;
  display: flex;
}
div.bg {
  background: #ccc;
  width: 100%;
  height: 100%;
  z-index: 0;
  left: 0px;
  top: 0px;
  position: absolute;
  display: flex;
}
<div class="container">
  <div class="c1">Content 1</div>
  <div class="c2">Content 2</div>
  <div class="bg">Background</div>
</div>

1

4 Answers 4

37

UPDATE: This issue has been resolved in Firefox (as of v52, released March 2017). The problem still exists in IE11.


Like you wrote in the question:

Firefox calculates absolute positioned div, and distributes space between divs like there are 3 divs in a row.

Firefox is considering the third div (.bg), which is absolutely positioned, an in-flow flex item and factoring it into its space-between calculation. (IE11 does this, too; Chrome and Edge ignore it.)

Clearly, this is not in compliance with the current flexbox spec:

4.1. Absolutely-Positioned Flex Children

As it is out-of-flow, an absolutely-positioned child of a flex container does not participate in flex layout.

Here are some workarounds:

Why not move the absolutely positioned div between the other two?

Instead of this:

<div class="container">
    <div class="c1">Content 1</div>
    <div class="c2">Content 2</div>
    <div class="bg">Background</div>
</div>

Try this:

<div class="container">
    <div class="c1">Content 1</div>
    <div class="bg">Background</div>
    <div class="c2">Content 2</div>
</div>

div.container {
  display: flex;
  flex-direction: row;
  width: 100%;
  height: 300px;
  justify-content: space-between;
  width: 100%;
  outline: 1px solid;
}
div.c1 {
  background: #aaeecc;
  width: 100px;
  position: relative;
  z-index: 50;
  top: 20px;
  display: flex;
}
div.c2 {
  background: #cceeaa;
  width: 200px;
  position: relative;
  z-index: 50;
  top: 20px;
  display: flex;
}
div.bg {
  background: #ccc;
  width: 100%;
  height: 100%;
  z-index: 0;
  left: 0px;
  top: 0px;
  position: absolute;
  display: flex;
}
<div class="container">
  <div class="c1">Content 1</div>
  <div class="bg">Background</div>
  <div class="c2">Content 2</div>
</div>

OR... remove .bg from the flex container:

<div class="container">
    <div class="c1">Content 1</div>
    <div class="c2">Content 2</div>
</div>
<div class="bg">Background</div>

div.container {
  display: flex;
  flex-direction: row;
  width: 100%;
  height: 300px;
  justify-content: space-between;
  width: 100%;
  outline: 1px solid;
}
div.c1 {
  background: #aaeecc;
  width: 100px;
  position: relative;
  z-index: 50;
  top: 20px;
  display: flex;
}
div.c2 {
  background: #cceeaa;
  width: 200px;
  position: relative;
  z-index: 50;
  top: 20px;
  display: flex;
}
div.bg {
  background: #ccc;
  width: 100%;
  height: 100%;
  z-index: 0;
  left: 0px;
  top: 0px;
  position: absolute;
  display: flex;
}
<div class="container">
  <div class="c1">Content 1</div>
  <div class="c2">Content 2</div>
</div>
<div class="bg">Background</div>

OR... use the flex order property to re-arrange the flex items.

Add this to your code:

.c2 { order: 1; }

div.container {
  display: flex;
  flex-direction: row;
  width: 100%;
  height: 300px;
  justify-content: space-between;
  width: 100%;
  outline: 1px solid;
}
div.c1 {
  background: #aaeecc;
  width: 100px;
  position: relative;
  z-index: 50;
  top: 20px;
  display: flex;
}
div.c2 {
  background: #cceeaa;
  width: 200px;
  position: relative;
  z-index: 50;
  top: 20px;
  display: flex;
  order: 1;
}
div.bg {
  background: #ccc;
  width: 100%;
  height: 100%;
  z-index: 0;
  left: 0px;
  top: 0px;
  position: absolute;
  display: flex;
}
<div class="container">
  <div class="c1">Content 1</div>
  <div class="c2">Content 2</div>
  <div class="bg">Background</div>
</div>

9
  • 4
    this is an incredibly thorough answer. Spot on mate.
    – Jhecht
    Commented Dec 31, 2016 at 0:16
  • 1
    Note that Firefox (and IE) have this behavior because it was required by an older revision of the flexbox spec -- the spec text on abspos flex children has changed quite a bit over time. Good news, though -- the current spec behavior will ship in Firefox 52! (which is slated for release on March 7th 2017)
    – dholbert
    Commented Jan 18, 2017 at 17:08
  • 2
    Well done and best answer here. I'd suggest using the "order" property before the other suggestions as this is really a CSS question, and source ordering a background div last is better and removing it from the container might not be possible.
    – Blorf
    Commented Jan 24, 2017 at 17:05
  • What is order doing here exactly? Commented Apr 17, 2018 at 21:26
  • It's moving the .c2 element, which is second in the source, to the third position (more info). @DrazenBjelovuk Commented Apr 17, 2018 at 21:28
16

It is happening because justify-content: space-between; Distribute items evenly The first item at the start, the last at the end. So just putt <div class="bg">Background</div> between <div class="c1">Content 1</div> and <div class="c2">Content 2</div> like this

<div class="container">
    <div class="c1">Content 1</div>
    <div class="bg">Background</div>
    <div class="c2">Content 2</div>

</div>

You can see the reason on https://developer.mozilla.org/en-US/docs/Web/CSS/justify-content

0
6

Sometimes it is not possible to re-order stuff, for example when using ::before and ::after. In those cases, you can manually order the elements.

In your case, you would need to do:

.c1 {
  order: -1;
}
.c2 {
  order: 10;
}

The order property is part of the flex spec and lets you re-order flex items (reference on MDN). It's very handy for multiple purposes, this included.

I used -1 because the value is ordinal, so setting it to a negative number ensures it precedes all other defaults and you don't need to specify the value for ::before. For the same reason, using 10 ensures that the second div comes last even if you add a bunch of elements to the container. You can increase that to 100 or whatever.

Still, Firefox's behaviour seems counterintuitive. position: absolute normally removes the element for the usual dom flow and I would expect that element to be removed from the flex flow as well, just like in Safari and Chrome. I am not sure whether the spec clarify this.

1

As an alternative you can use the flex property inside the content selector(s):

    div.c1 {
      background: #aaeecc;
      width: 100px;
      position: relative;
      z-index: 50; top: 20px;
      display: flex;

      flex: 1; /* add this */
    }

This will set the flex-grow. It might not be exactly what you need, but maybe it helps somebody else that can not reorder the content divs or take them out from the flex wrapper.

Here is the demo: https://jsfiddle.net/s18do03e/14/

Not the answer you're looking for? Browse other questions tagged or ask your own question.