Place a set of small divs above and below one full-width large div depending on space available

Viewed 82

Suppose I have a layout of some arbitrary number of small divs, and one big div. I want to have one row of the small divs, as many as can fit in the container width, followed by the big div, which takes up the full width. It will also flex in height, maintaining its aspect ratio (contains an image). Any remaining small divs then continue after the big one. As the container expands and contracts, small divs would move below/above the big one as space allows. Hoping for CSS-only solution (open to JS though).

So initially:

---------------------
| d1 | d2 | d3 | d4 |
---------------------
|         DB        |
---------------------
| d5 | d6 | d7 | d8 |
---------------------

Then the container shrinks, causing this layout:

----------------
| d1 | d2 | d3 |
----------------
|      DB      |
----------------
| d4 | d5 | d6 | 
----------------
| d7 | d8 |

I'm envisioning a flexbox solution but haven't been able to make it work.

3 Answers

If the small divs can be the same size, CSS Grid could work.

Flexbox will not work here, because it doesn’t let you define this logic of “filling the first line with as many items as possible, then placing the one big item, then continuing”. In Flexbox, you have to define the order in which the items are to be placed into the flex lines.

CSS Grid lets you place grid items more arbitrarily. So you could make sure your big div always stretches across all columns, and it’s placed in the second row. And all the small divs will fill up the remaining space.

There’s one caveat, though. You can’t make your small divs sized based on their content if you’re going to use CSS Grid to create “as many columns as will fit at this screen size”.

Here’s an example of a working solution. Each column with each small div will be at least 200px wide, and it will grow a bit if there’s any remaining free space horizontally. Obviously you can pick your own minimum size for each small div.

.grid-container {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
}

div {
  padding: 24px;
  text-align: center;
  border: 1px solid black;
}

div:nth-of-type(odd) {
  background-color: whitesmoke;
}

.big-div {
  grid-row: 2;
  grid-column: 1 / -1;
}
<main class="grid-container">
  <div>Small</div>
  <div>Small</div>
  <div>Small</div>
  <div class="big-div">Big</div>
  <div>Small</div>
  <div>Small</div>
  <div>Small</div>
  <div>Small</div>
  <div>Small</div>
</main>

Mavbe you can try with css grid and grid-area

.parent {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  justify-items: center;
}

.div5 { grid-area: 2 / 1 / 3 / 5; }


@media (max-width: 400px) {
  .parent {
    grid-template-columns: repeat(3, 1fr);
  }

  .div5 { grid-area: 2 / 1 / 3 / 4; } 
}
<div class="parent">
  <div class="div1">1</div>
  <div class="div2">2</div>
  <div class="div3">3</div>
  <div class="div4">4</div>
  <div class="div5">DB</div>
  <div class="div6">5</div>
  <div class="div7">6</div>
  <div class="div8">7</div>
  <div class="div9">8</div>
</div>

If the height of the divs is known, you can use flexbox with a trick.

Set margin on the first small div to create space in the second row, the move big into this space

hover the snippet to see it work

.container {
  display: flex;
  width: 200px;
  border: solid 1px black;
  position: relative;
  flex-wrap: wrap;
  transition: linear 5s;
}

html:hover .container {
    width: 900px;
}

div {
  width: 150px;
  height: 50px;
  flex-shrink: 0;
  flex-grow: 0;
  margin: 4px;
  background-color: lightgreen;
}

#first {
   margin-bottom: 60px;  /* big height */
}

div.big {
  width: 100%;
  height: 50px;
  background-color: lightblue;
  position: relative;
  position: absolute;
  transform: translate(0px, 55px);   /* small height */
}
<main class="container">
  <div class="big">Big</div>
  <div id="first">1</div>
  <div>2</div>
  <div>3</div>
  <div>4</div>
  <div>5</div>
  <div>6</div>
  <div>7</div>
  <div>8</div>
</main>

Related