Color of stacked semi-transparent boxes depends on order?

Simply because in both cases the combination of colors is not the same due to how opacity of the top layer affect the color of the bottom layer.

For the first case, you see 50% of blue and 50% of transparent in the top layer. Through the transparent part, you see 50% of red color in the bottom layer (so we only see 25% of red in total). Same logic for the second case (50% of red and 25% of blue); thus you will see different colors because for both cases we don't have the same proportion.

To avoid this you need to have the same proportion for both your colors.

Here is an example to better illustrate and show how we can obtain same color:

In the top layer (the inner span) we have 0.25 of opacity (so we have 25% of the first color and 75% of transparent) then for the bottom layer (the outer span) we have 0.333 opacity (so we have 1/3 of 75% = 25% of the color and the remaining is transparent). We have the same proportion in both layers (25%) so we see the same color even if we reverse the order of layers.

.a {
  background-color: rgba(255, 0, 0, 0.333)
}

.b {
  background-color: rgba(0, 0, 255, 0.333)
}

.a > .b {
  background-color: rgba(0, 0, 255, 0.25)
}
.b > .a {
  background-color: rgba(255, 0, 0, 0.25)
}
<span class="a"><span class="b">          Color 1</span></span>
<span class="b"><span class="a">Different Color 2</span></span>

As a side note, the white background is also affecting the rendering of the colors. Its proportion is 50% which will make the logical result of 100% (25% + 25% + 50%).

You may also notice that it won't be possible to have same proportion for our both colors if the top layer is having an opacity bigger than 0.5 because the first one will have more than 50% and it will remain less than 50% for the second one:

.a {
  background-color: rgba(255, 0, 0, 1) /*taking 40% even with opacity:1*/
}

.b {
  background-color: rgba(0, 0, 255, 1) /*taking 40% even with opacity:1*/
}

.a > .b {
  background-color: rgba(0, 0, 255, 0.6) /* taking 60%*/
}
.b > .a {
  background-color: rgba(255, 0, 0, 0.6) /* taking 60%*/
}
<span class="a"><span class="b">          Color 1</span></span>
<span class="b"><span class="a">Different Color 2</span></span>

The common trivial case is when the top layer is having opacity:1 which make the top color with a proportion of 100%; thus it's an opaque color.


For a more accurate and precise explanation here is the formula used to calculate the color we see after the combination of both layersref:

ColorF = (ColorT*opacityT + ColorB*OpacityB*(1 - OpacityT)) / factor

ColorF is our final color. ColorT/ColorB are respectively the top and bottom colors. opacityT/opacityB are respectively the top and bottom opacities defined for each color:

The factor is defined by this formula OpacityT + OpacityB*(1 - OpacityT).

It's clear that if we switch the two layers the factor won't change (it will remain a constant) but we can clearly see that the proportion for each color will change since we don't have the same multiplier.

For our initial case, both opacities are 0.5 so we will have:

ColorF = (ColorT*0.5 + ColorB*0.5*(1 - 0.5)) / factor

Like explained above, the top color is having a proportion of 50% (0.5) and the bottom one is having a proportion of 25% (0.5*(1-0.5)) so switching the layers will also switch these proportions; thus we see a different final color.

Now if we consider the second example we will have:

ColorF = (ColorT*0.25 + ColorB*0.333*(1 - 0.25)) / factor

In this case we have 0.25 = 0.333*(1 - 0.25) so switching the two layers will have no effect; thus the color will remain the same.

We can also clearly identify the trivial cases:

  • When top layer is having opacity:0 the formula is equal to ColorF = ColorB
  • When top layer is having opacity:1 the formula is equal to ColorF = ColorT

You can use the css property, mix-blend-mode : multiply (limited browser support)

.a {
  background-color: rgba(255, 0, 0, 0.5);
  mix-blend-mode: multiply;
}

.b {
  background-color: rgba(0, 0, 255, 0.5);
  mix-blend-mode: multiply;
}

.c {
  position: relative;
  left: 0px;
  width: 50px;
  height: 50px;
}

.d {
  position: relative;
  left: 25px;
  top: -50px;
  width: 50px;
  height: 50px;
}
<span class="a"><span class="b">          Color 1</span></span>
<span class="b"><span class="a">Different Color 2</span></span>

<div class="c a"></div>
<div class="d b"></div>

<div class="c b"></div>
<div class="d a"></div>