Search code examples
htmlcssflexboxcss-floatcss-grid

How to pull out an element to the right in responsive design?


Here's the layout I'm trying to achieve:

       Mobile:                            Desktop:
+-------------------+        +-----------+---------------------+
|         C         |        |     C     |                     |
+-------------------+        +-----------+                     |
|                   |        |     P     |                     |
|                   |        +-----------+                     |
|         V         |        |           |          V          |
|                   |        |           |                     |
|                   |        |           |                     |
+-------------------+        |           |                     |
|         P         |        |           |                     |
+-------------------+        +-----------+---------------------+
         100%                    300px        100% - 300px

So if the screen is wide enough (min-width: 960px), element V is pulled out from between the other two, and moved to the right.

Note that none of the elements (including the outer container) have a fixed, known height. They must all be automatically sized to fit their contents.

The mobile version is also a sensible order for the DOM, so let's use this HTML:

<div class="outer">
  <div class="C"></div>
  <div class="V"></div>
  <div class="P"></div>
</div>

I first tried to achieve the desktop layout with flexbox:

@media screen and (min-width: 960px) {
  .outer {
    display: flex;
    flex-direction: column;
  }
  .C { order: 1; width: 300px; }
  .V { order: 3; }
  .P { order: 2; width: 300px; }
}

This takes care of the reordering, but there seems to be no way to force a wrap between P and V without setting fixed heights. So the elements remain stacked on top of each other.

I also tried pulling V out using a float:

@media screen and (min-width: 960px) {
  .C { width: 300px; }
  .V { width: calc(100% - 300px); margin-left: 300px; float: right; }
  .P { width: 300px; }
}

But then it ends up below C. To fix that, I would have to change the DOM order, which I'd rather not do for accessibility reasons.

I could also pull V out with absolute positioning:

@media screen and (min-width: 960px) {
  .outer { position: relative; }
  .C { width: 300px; }
  .V { width: calc(100% - 300px); right: 0; top: 0; }
  .P { width: 300px; }
}

The problem with that is that V no longer affects the height of the outer div, and starts overlapping the content below outer.

Finally I considered CSS grid, but it would reduce browser compatibility.

Is there a way to create this layout without resorting to ugly hacks?


Solution

  • Add more floating:

    @media screen and (min-width: 960px) {
      .C {
        width: 300px;
        float: left;
      }
      .V {
        width: calc(100% - 300px);
        float: right;
      }
      .P {
        width: 300px;
        clear: left; /* clear only left to go under C */
      }
      .outer {
        overflow: auto; /* create a BFC to avoid the float getting outside the container */
      }
    }
    
    .outer {
      margin:10px;
    }
    <div class="outer">
      <div class="C" style="height:50px;background:red"></div>
      <div class="V" style="height:150px;background:blue"></div>
      <div class="P" style="height:60px;background:green"></div>
    </div>
    
    
    <div class="outer">
      <div class="C" style="height:50px;background:red"></div>
      <div class="V" style="height:100px;background:blue"></div>
      <div class="P" style="height:100px;background:green"></div>
    </div>
    
    <div class="outer">
      <div class="C" style="height:100px;background:red"></div>
      <div class="V" style="height:50px;background:blue"></div>
      <div class="P" style="height:50px;background:green"></div>
    </div>

    Or CSS grid like below:

    @media screen and (min-width: 960px) {
      .V {
        grid-row:span 3;
      }
      .outer {
        display:grid;
        grid-template-columns:300px 1fr;
        align-items:start;
      }
    }
    
    .outer {
      margin:10px;
    }
    <div class="outer">
      <div class="C" style="height:50px;background:red"></div>
      <div class="V" style="height:150px;background:blue"></div>
      <div class="P" style="height:60px;background:green"></div>
    </div>
    
    
    <div class="outer">
      <div class="C" style="height:50px;background:red"></div>
      <div class="V" style="height:100px;background:blue"></div>
      <div class="P" style="height:100px;background:green"></div>
    </div>
    
    <div class="outer">
      <div class="C" style="height:100px;background:red"></div>
      <div class="V" style="height:50px;background:blue"></div>
      <div class="P" style="height:50px;background:green"></div>
    </div>