Search code examples
htmlcssflexboxalignmentcss-grid

Layout depending on number of elements


I have a wrapper that can contain 2, 3 or 4 elements (I don't know it in advance because every element renders itself, depending on API response).

If there are 3 (or less) I want them to stack like:

3 elements layout

No big deal. But when there are 4 of them, I need this other layout:

4 elements layout

So far, I thought CSS Grid would be the way to go and I tried:

/* Just to add some interaction to the demo */

const w = document.getElementById("wrapper");
let x;

function toggle(event) {

  const d = document.getElementById("D");
  return d 
    ? 
      x = d.cloneNode() && w.removeChild(d) 
    : w.appendChild(x);
}
#wrapper {
  width: 100%;
  max-width: 500px;
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-gap: 10px;
}



.item {
  grid-column-start: span 2
}

.item:nth-last-child(1),
.item:nth-last-child(2) {
  grid-column-start: auto;
}


/* Non-relevant CSS here: */

button {
  margin: 20px auto;
  font-size: 16px;
  padding: 3px 6px;
  border-radius: 6px;
}

#A { background: #7984f7 }
#B { background: #cb8af8 }
#C { background: #8cd4fb }
#D { background: #97f8d8 }

.item {
  border-radius: 6px;
  padding: 10px 0;
  font-family: sans-serif;
  font-weight: bold;
  color: white;
  text-align: center;
}
<div id="wrapper">
  <div id="A" class="item">A</div>
  <div id="B" class="item">B</div>
  <div id="C" class="item">C</div>
  <div id="D" class="item">D</div>
</div>

<button onclick="toggle()">Click me!</button>

But it doesn't work for 3 elements... In fact, I've tried many things (all CSS Grid related) and there's probably an easier solution I can't see right now... Any help will be much appreciated!


Solution

  • Using CSS grid:

    1. display all items in two columns by default
    2. display items 1 and 2 in a single column
    3. display item 3 in a single column only if it's the last item

    One side effect is that if there are ever more than 4 items, the additional ones will display in two columns.

    /* Just to add some interaction to the demo */
    
    const w = document.getElementById("wrapper");
    let x;
    
    function toggle(event) {
    
      const d = document.getElementById("D");
      return d 
        ? 
          x = d.cloneNode() && w.removeChild(d) 
        : w.appendChild(x);
    }
    #wrapper {
      width: 100%;
      max-width: 500px;
      display: grid;
      grid-template-columns: 1fr 1fr;
      grid-gap: 10px;
    }
    
    
    
    .item {
      grid-column-start: auto;
    }
    
    .item:nth-child(1),
    .item:nth-child(2),
    .item:nth-child(3):last-child {
      grid-column-start: span 2;
    }
    
    
    /* Non-relevant CSS here: */
    
    button {
      margin: 20px auto;
      font-size: 16px;
      padding: 3px 6px;
      border-radius: 6px;
    }
    
    #A { background: #7984f7 }
    #B { background: #cb8af8 }
    #C { background: #8cd4fb }
    #D { background: #97f8d8 }
    
    .item {
      border-radius: 6px;
      padding: 10px 0;
      font-family: sans-serif;
      font-weight: bold;
      color: white;
      text-align: center;
    }
    <div id="wrapper">
      <div id="A" class="item">A</div>
      <div id="B" class="item">B</div>
      <div id="C" class="item">C</div>
      <div id="D" class="item">D</div>
    </div>
    
    <button onclick="toggle()">Click me!</button>