25

I've got an unknown number of elements in a container that need to wrap with no margins on the outside, but minimum margins between them.

I also need these to be justified with space-between and the last row left aligned.

I'm trying to do this with flexbox like so:

.outside {
  border: 1px solid black;
}
.container {
  margin: -5px;
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
}
.container:after {
  content: '';
  flex: auto;
}
.box {
  background: red;
  width: 50px;
  height: 50px;
  margin: 5px;
}
<div class="outside">
  <div class="container">
    <div class="box"></div>
    <div class="box"></div>
    <div class="box"></div>
    <div class="box"></div>
    <div class="box"></div>
    <div class="box"></div>
    <div class="box"></div>
    <div class="box"></div>
    <div class="box"></div>
    <div class="box"></div>
    <div class="box"></div>
    <div class="box"></div>
    <div class="box"></div>
    <div class="box"></div>
    <div class="box"></div>
    <div class="box"></div>
    <div class="box"></div>
    <div class="box"></div>
    <div class="box"></div>
    <div class="box"></div>
    <div class="box"></div>
  </div>
</div>

View JSFiddle

This works correctly, except that the spacing on the last row is off, as you can see in the screenshot:

enter image description here

Is there any way to get this to work if we don't know how many columns there will be (using flexbox or something else other than javascript)?

Michael Benjamin
  • 346,931
  • 104
  • 581
  • 701
James Simpson
  • 13,488
  • 26
  • 83
  • 108
  • Wait some years. Flexbox level 2 will probably add some feature to address this problem. – Oriol Dec 02 '16 at 16:35
  • 2
    Last row alignment in a flex container is probably the #1 missing feature in flexbox today. Another common problem is the various deficiencies with `column wrap`. Hopefully both are addressed in the next version of the flexbox spec. – Michael Benjamin Dec 02 '16 at 16:45
  • Is there a way to do this without flexbox that we might be overlooking? – James Simpson Dec 02 '16 at 16:48

3 Answers3

16

Last row alignment is a common problem with flexbox.

One method to consider is using invisible flex items after the last visible item. For short, I just call them "trailing phantom items".

.outside {
  border: 1px solid black;
}
.container {
  margin: -5px;
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
}
.container:after {
  content: '';
  flex: auto;
}
.box {
  background: red;
  width: 50px;
  height: 50px;
  margin: 5px;
}

.hidden {
  visibility: hidden;
  margin: 0 5px;
  height: 0;
}
<div class="outside">
  <div class="container">
    <div class="box"></div>
    <div class="box"></div>
    <div class="box"></div>
    <div class="box"></div>
    <div class="box"></div>
    <div class="box"></div>
    <div class="box"></div>
    <div class="box"></div>
    <div class="box"></div>
    <div class="box"></div>
    <div class="box"></div>
    <div class="box"></div>
    <div class="box hidden"></div>
    <div class="box hidden"></div>
    <div class="box hidden"></div>
    <div class="box hidden"></div>
    <div class="box hidden"></div>
    <div class="box hidden"></div>
    <div class="box hidden"></div>
    <div class="box hidden"></div>
    <div class="box hidden"></div>
    <div class="box hidden"></div>
    <div class="box hidden"></div>
    <div class="box hidden"></div>
    <div class="box hidden"></div>
  </div>
</div>
Michael Benjamin
  • 346,931
  • 104
  • 581
  • 701
  • 4
    This would only work if we knew how many were in a row (I noticed I posted the wrong link, it shouldn't have had the 500px width in the fiddle, which I've now updated). – James Simpson Dec 02 '16 at 16:27
  • 1
    Yes, I see you updated your code to remove the width on the container. Without this limit, you may need to resort to JS, as I don't believe that current CSS technology can solve the problem. I'll leave my answer as-is for general reference. – Michael Benjamin Dec 02 '16 at 17:16
  • You don't really need to know how many items are in a row. You just need to know the **maximum** number of items than can be in a row, and set as many hidden boxes as this maximum – vals Dec 02 '16 at 17:37
  • 1
    I have editted the snippet to show it. Hope you don't mind :-) – vals Dec 02 '16 at 17:40
  • 1
    @vals, but how can we know the maximum, when a screen width can be anything? – Michael Benjamin Dec 02 '16 at 17:41
  • Of course you can't know it always, but in most cases .. Say thay you fix it for a maximum width of 2000px ... is it a real issue that somebody will use a wider screen and see it not quite aligning ? Anyway, I just wanted to make the issue clearer... – vals Dec 02 '16 at 17:57
  • 1
    This works like a charm for me - thank you! :) – iamkeir Aug 28 '17 at 14:27
  • 2
    It's horrifically dirty, but it works and I can't find a better way. So +1 :) – Steve D Aug 23 '18 at 13:47
  • how does bootstrap achieve this then? – vikrant Jul 25 '20 at 05:21
1

Michael is right that the problem is you can't do space-between and then select the last row to be flex-start. The part that sticks out to me though is the specified width. Is that important?

If not, the usual way to do this would be to use media queries to control how many items are displayed per row. You can set a lots of media query steps to make sure the items don't stretch too much, but that way the space between more closely lines up with your normal grid gutters and it makes the "last row problem" go away. Think that would work?

Example: http://codepen.io/anon/pen/JbpPKa

  • 5
    The width is not supposed to be there and isn't in the code in the question. What is making this difficult is the fact that it must be responsive and we don't know how wide the container will be, thus no clue how many elements per row. – James Simpson Dec 02 '16 at 17:06
1

Stack Overflow won't allow me to comment on Michael_B's answer, but I did want to illustrate how calculating the number of phantom elements to be appended can be accomplished with JavaScript for anyone who is trying to solve this problem.

  /**
   * @param {Integer} numElements The number of elements you're displaying.
   * @param {Number} element Width Width, in pixels, of each element.
   * @param {Number} margin Width, in pixels. Your minimum target margin between items. 2x the margin on each individual item.
   * @param {Number} containerWidth Width, in pixels, of the containing element.
   */
  const getNumPhantomElements = ({numElements, elementWidth, margin, containerWidth}) => {
    const elementsPerRow = Math.floor(containerWidth / (elementWidth + margin));
    const elementsInLastRow = numElements % elementsPerRow;
    const numPhantomElements = elementsPerRow - elementsInLastRow;

    return numPhantomElements;
  };

In this case:

const containerWidth = document.querySelector('.container').offsetWidth;
const numPhantomElements = getNumPhantomElements({
  numElements: 21,
  elementWidth: 50,
  margin: 10, // 2x 5px margin on each box
  containerWidth
}); // Append this many elements (output depends on your viewport size)
Benji Kay
  • 41
  • 3