61

My problem is that I want the flexbox with variable range width, and all works well, but not on the last row. I want the same dimension for all children even where the row is not full of children (the last row).

#products-list {
    position:relative;
    display: flex;
    flex-flow: row wrap;
    width:100%;
}

#products-list .product {
    min-width:150px;
    max-width:250px;
    margin:10px 10px 20px 10px;
    flex:1;
}

I created a dynamic situation in jsFiddle

My flex divs can shrink until 150px and grow up to 250px, but all must be with the same size (and obviously I want a CSS solution, with JS I know the way).

Michael Benjamin
  • 346,931
  • 104
  • 581
  • 701
Baro
  • 5,300
  • 2
  • 17
  • 39

10 Answers10

76

Unfortunately, in the current iteration of flexbox (Level 1), there is no clean way to solve the last-row alignment problem. It's a common problem.

It would be useful to have a flex property along the lines of:

  • last-row
  • last-column
  • only-child-in-a-row
  • alone-in-a-column

This problem does appear to be a high priority for Flexbox Level 2:

Although this behavior is difficult to achieve in flexbox, it's simple and easy in CSS Grid Layout:

In case Grid is not an option, here's a list of similar questions containing various flexbox hacks:

Michael Benjamin
  • 346,931
  • 104
  • 581
  • 701
  • 39
    This answer has the same problem as books these days: too much information => nobody reads it. Would have been nice to have a TL;DR; section (quick fix). – tao Apr 28 '17 at 10:05
  • 4
    @AndreiGheorghiu, I know of no quick fix to this problem. In fact, the problem itself varies in nature. If you have a universal solution, you should post an answer. – Michael Benjamin Apr 28 '17 at 12:09
  • Also, for somebody reading this answer solely for educational purposes, I think you are correct. Few, if any, people will go through the entire list of links. – Michael Benjamin Apr 28 '17 at 12:10
  • 4
    But for somebody visiting this page who *desperately needs a solution*, I would argue they will go through each and every link hoping to find a method that works. Based on the upvotes to this answer, and [**this one**](http://stackoverflow.com/q/33891709/3597276) and [**this one**](http://stackoverflow.com/a/36118012/3597276) (see "Side Note" at bottom), I would say that at least some people find the references useful. – Michael Benjamin Apr 28 '17 at 12:11
  • 1
    @Michael_B, I didn't have the patience to go through all the links and check if anyone came up with a js fix for it, so I made [my own](https://stackoverflow.com/a/44805164/1891677), for the special case when there's no determined grid (or known child width). Just variable width children, last row should be left-aligned while previous rows should stretch to fill full size. – tao Jun 28 '17 at 14:59
6

As a quick and dirty solution one can use:

.my-flex-child:last-child/*.product:last-child*/ {
  flex-grow: 100;/*Or any number big enough*/
}
Boris D. Teoharov
  • 2,319
  • 4
  • 30
  • 49
  • 1
    Shouldn't that be `flex-grow: 0`? OP wants "the same dimension for all children even where the row is not full of children". `flex-grow: 100` would potentially make the element fill the width of it's parent. – fullStackChris Apr 22 '20 at 10:40
  • 1
    Even if flex-grow: 0, this would still cause the second to last item to grow to take that width. – Matt Eland Jul 08 '20 at 21:33
2

A simple trick adds a flexible space to fill the rest of the last row:

#products-list{
    display:flex;
    flex-flow: row wrap;
    justify-content:space-between;
}
#products-list::after {
    content: "";
    flex: auto;
    flex-basis: 200px;/*your item width*/
    flex-grow: 0;
}

But you shouldn't use margins on items then. Rather wrap them into containers with padding.

Fanky
  • 1,673
  • 1
  • 18
  • 20
2

You could try using grid instead of flexbox here:

#products-list {

  display: grid;
  grid-gap: 5px;
  grid-template-columns: repeat(auto-fit, minmax(100px, 250px)); //grid automagic
  justify-content: start; //start left

}

Fiddle link

Dostrelith
  • 922
  • 5
  • 13
2

If all your rows have the same number of items, you can use :nth-last-child. For example, if all the rows have 3 items, you can do something like this to remove the margin of the last 3 items:

.container{
  display: flex;
  flex-wrap: wrap;
  background: yellow;
}

.item{
  width: calc((100% - 2*10px)/3);
  height: 50px;
  background: blue;
  color: white;
  margin-right: 10px;
  margin-bottom: 10px;
  padding: 5px;
  box-sizing: border-box;
}

/* last item of each row */
.item:nth-child(3n){
  margin-right: 0;
  font-size: 150%;
}

/* last 3 items */
.item:nth-last-child(-n+3){
  margin-bottom: 0;
  background: green;
}
<div class="container">
  <div class="item" >1</div>
  <div class="item" >2</div>
  <div class="item" >3</div>
  <div class="item" >4</div>
  <div class="item" >5</div>
  <div class="item" >6</div>
  <div class="item" >7</div>
</div>
David
  • 2,942
  • 33
  • 16
1

I used this workaround, even if it's not very elegant and it doesn't use the power of Flexbox.

It can be carried out on the following conditions:

  • All the items have the same width
  • The items have a fixed width
  • You use SCSS/SASS (can be avoided though)

If this is the case, you can use the following snippet:

 $itemWidth: 400px;
 $itemMargin: 10px;

html, body {
  margin: 0;
  padding: 0;
}

.flex-container {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  margin: 0 auto;
  border: solid 1px blue;
}

@for $i from 1 through 10 {
  @media only screen and (min-width: $i * $itemWidth + 2 * $i * $itemMargin) {
    .flex-container {
      width: $i * $itemWidth + 2 * $i * $itemMargin;
    }
  }
}

.item {
  flex: 0 0 $itemWidth;
  height: 100px;
  margin: $itemMargin;
  background: red;
}
<div class="flex-container">
  <div class="item"></div>
  <div class="item" style="flex: 500 0 200px"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
</div>

Here I have created an example on codepen which also implements margin.

The second and the third conditions can be avoided respectively using css variables (if you decided to provide support for it) and compiling the above scss snippet.

Well, it's true, we could do it also before flexbox, but display: flex can be still essential for a responsive design.

TylerH
  • 20,799
  • 66
  • 75
  • 101
1

There is a great solution that works always. add a div with class product (The same class for other items that are under flex) and add a style for this div:height:0px; you need to add as many dives that are possible to be in one row.

<div class="product" style="height:0px">

as many that can be in one row. That's all. Works always.

simi
  • 173
  • 2
  • 7
  • 2
    I'd say it's more of a hack than a great solution, especially in situations where you don't know the count upfront. – crollywood Oct 11 '21 at 11:47
  • It is a greate solution, because if you don't know the count upfront and you want that the last row will be alway!!! perfect, then it is a very simple way. work greate always. works on all sizes. Nothing can be better than this solutinon. – simi Oct 12 '21 at 13:48
1

I was facing this same issue where I wanted to have a variable number of items in a resizable container. I wanted to use all of the horizontal space, but have all of the flex items at the same size.

I ultimately came up with a javascript approach that dynamically added padding spacers as the container was resized.

    function padLastFormRow() {
        let topList = [];
        let nSpacersToAdd = 0;
        $('#flexContainer').find('.formSpacer').remove();
        $('#flexContainer').find('.formItem').each(function(i, formItem) {
            topList.push($(formItem).position().top);
        });

        let allRowLengths = getFlexLineLengths(topList);
        let firstRowLength  = allRowLengths[0];
        let lastRowLength   = allRowLengths[((allRowLengths.length) - 1)];

        if (lastRowLength < firstRowLength) {
            nSpacersToAdd = firstRowLength - lastRowLength ;
        }

        for (var i = 1; i <= nSpacersToAdd; i ++) {
            $('#flexContainer').append(formSpacerItem);
        }
    }

Please see my Fiddle: http://jsfiddle.net/Harold_Buchman/z5r3ogye/11/

HaroldB2
  • 21
  • 4
1

I was having a similar challenge with menu rows. I wanted more spacing on the top of the second row of menu items.

The use of flex-box's row-gap worked well.

https://developer.mozilla.org/en-US/docs/Web/CSS/row-gap

.menu {
  display: flex;
  flex-wrap: wrap;
  row-gap: 10px;
 }

This added a margin-top type effect to menu items were wrapped to the second line.

1

If all your rows have the same number of items, you can use :nth-last-child. For example, if all the rows have 3 items, you can do something like this:

.container{
  display: flex;
  flex-wrap: wrap;
  background: yellow;
}

.item{
  width: calc((100% - 2*10px)/3);
  height: 50px;
  background: blue;
  color: white;
  margin-right: 10px;
  margin-bottom: 10px;
  padding: 5px;
  box-sizing: border-box;
}

// last item of each row
.item:nth-child(3n){
  margin-right: 0;
  background: green;
}

// last 3 items
.item:nth-last-child(-n+3){
  margin-bottom: 0;
  font-size: 150%;
}
<div class="container">
  <div class="item" >1</div>
  <div class="item" >2</div>
  <div class="item" >3</div>
  <div class="item" >4</div>
  <div class="item" >5</div>
  <div class="item" >6</div>
  <div class="item" >7</div>
</div>
David
  • 2,942
  • 33
  • 16