0

I am trying to make the CSS grid automatically size the final set of elements such that they autofill the last row if the amount of entries does not match the column count.

See image:

enter image description here

Michael Benjamin
  • 346,931
  • 104
  • 581
  • 701
Liam Pillay
  • 592
  • 1
  • 4
  • 19

3 Answers3

1

You can do so by selecting the last odd child and providing grid-column: 1 / -1.

.grid {
  border: 2.5px solid gray;
  border-radius: 10px;
  width: fit-content;
  height: fit-content;
  padding: 1em;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1em;
}

.grid__item {
  min-width: 100px;
  min-height: 100px;
  border: 2.5px solid palevioletred;
  text-align: center;
  display: flex;
  align-items: center;
  justify-content: center;
}

.grid__item:nth-child(2n-1):last-child {
  background: pink;
  grid-column: 1 / -1;
}
<div class="grid">
  <div class="grid__item">1</div>
  <div class="grid__item">2</div>
  <div class="grid__item">3</div>
  <div class="grid__item">4</div>
  <div class="grid__item">5</div>
  <div class="grid__item">6</div>
  <div class="grid__item">7</div>
</div>
Sachin Som
  • 1,005
  • 3
  • 8
  • Great fix for a 2x2 grid which actually solves my problem, but to better answer the question as well, is there a way to do this that solves it for a 3x3 grid with only 1 or 2 entries in the last row? – Liam Pillay Apr 13 '23 at 05:16
  • no there is not. You don't use a grid for that task. – tacoshy Apr 13 '23 at 06:06
1

You need display: flex and a bit of math.

First, create a container element and its items:

<div id="grid">
  <div class="item">
    <!-- ... -->
  </div>
  <!-- ...and more. -->
</div>

Then, make it a flexbox with flex-wrap: wrap:

#grid {
  display: flex;
  flex-wrap: wrap;
}

Let the number of column we want be c. By observation we know that there are c columns (by definition), c - 1 gaps and 2 paddings, which sum up to 100% of the container's width:

┌──────────────────────────┐
│ ┌──┐ ┌──┐ ┌──┐ ┌──┐ ┌──┐ │
│p│c1│g│c2│g│c3│g│c4│g│c5│p│
│ └──┘ └──┘ └──┘ └──┘ └──┘ │
│                          │
│                          │
│                          │
│                          │
└──────────────────────────┘

This means an item's width can be calculated as:

w = (100% - (g * (c - 1)) - p * 2) / c

We translate that to CSS accordingly, and add flex-grow: 1 to make the last items take all remaining spaces:

.item {
  flex-grow: 1;
  width: calc((100% - var(--gap) * (var(--columns) - 1) - var(--padding) * 2) / var(--columns));
}

Try it:

const grid = document.querySelector('#grid');
const input = document.querySelector('input');

input.addEventListener('input', function() {
  grid.style.setProperty('--columns', this.value);
});

const l = 30 + Math.random() * 71 | 0;

for (let i = 1; i < l; i++) {
  const item = document.createElement('div');
  item.classList.add('item');
  item.textContent = i;
  grid.appendChild(item);
}
#grid {
  --columns: 3;
  --padding: 1em;
  --gap: 1em;
  
  display: flex;
  flex-flow: row wrap;
  gap: var(--gap);
  border: 1px solid #000;
  padding: var(--padding);
}

.item {
  flex-grow: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100px;
  width: calc(
    (
      100% -
      var(--gap) * (var(--columns) - 1) -
      var(--padding) * 2
    ) /
    var(--columns)
  );
  background: #ddd;
}

label {
  position: fixed;
  bottom: 2em;
  left: 2em;
}
<div id="grid"></div>

<label>
  Columns:
  <input type="range" min="1" max="10" step="1" value="3">
</label>
InSync
  • 4,851
  • 4
  • 8
  • 30
  • Don't calculate the padding and on top of that the border. Make your life easy and use `box-sizing: border-box`. For the parent element, the default `content-box` applies where the padding is not used for the width in the first place. No need to calculate the padding there. – tacoshy Apr 13 '23 at 06:21
  • Since that aspect has already been covered in your answer, I'm not going to add it to mine. Good call, nevertheless. – InSync Apr 13 '23 at 06:40
1

Overall this is not a task for CSS-Grid but Flexbox. In Flexbox you can make use of flex-grow: 1 to make the elements grow to fill the remaining space.

Flexbox can make use of flex-wrap: wrap so that elements that would overflow would break to the next row. The tricky part here is, that as such the width of every call must be calculated correctly.

For the calculations, I recommend the usage of CSS variables. One variable for the gaps and then one variable for the columns. The correct width can be calculated by using that algorithm:

width = ((100% - (size of gaps * (amount of columns - 1))) / amount of columns)

To work correctly the containers box-model should be set to default (content-box) while the cards box model must be set to border-box so that the padding and border are included in the width:

:root {
  --gap: 1em;
}

section {
  display: flex;
  flex-wrap: wrap;
  gap: var(--gap);
}

.col-2 {
  --column: 2;
}

.col-3 {
  --column: 3; 
}

section > div {
  flex-grow: 1;
  box-sizing: border-box;
  width: calc((100% - (var(--gap) * (var(--column) - 1))) / var(--column));
}


/* for demo purpose only */
section > div {
  border: 2px dashed red;
  text-align: center;
  padding: 2em;
}
<h1>Grid with 2 columns and 3 cards</h1>
<section class="col-2">
  <div>1</div>
  <div>2</div>
  <div>3</div>
</section>

<h1>Grid with 3 columns and 4 cards</h1>
<section class="col-3">
  <div>1</div>
  <div>2</div>
  <div>3</div>
  <div>4</div>
</section>

<h1>Grid with 3 columns and 5 cards</h1>
<section class="col-3">
  <div>1</div>
  <div>2</div>
  <div>3</div>
  <div>4</div>
  <div>5</div>
</section>
tacoshy
  • 10,642
  • 5
  • 17
  • 34