Flex: Wrapped Items with Same Width as The Rest

How to keep wrapped flex-items the same width as the elements on the previous row?

TL;DR

This is not something I'd call a solution per se, but it's a rather elegant workaround that only uses media queries, and more importantly no JavaScript!

Mixin (SCSS):

@mixin flex-wrap-fix($flex-basis, $max-viewport-width: 2000px) {
flex-grow: 1;
flex-basis: $flex-basis;
max-width: 100%;

$multiplier: 1;
$current-width: 0px;

@while $current-width < $max-viewport-width {
$current-width: $current-width + $flex-basis;
$multiplier: $multiplier + 1;

@media(min-width: $flex-basis * $multiplier) {
max-width: percentage(1/$multiplier);
}
}
}

Usage:

Apply the mixin to your flex item:

.flex-item {
@include flex-wrap-fix(100px)
}

Update:

The above mixin should do the trick, as long as you your flex container width matches your viewport size, as is the case in OP's example. Media queries won't help you otherwise, because they're always based on the viewport width. However, you could use the css-element-queries library and its element queries instead of browser media queries. Here's a mixin that you can apply to the flex container:

@mixin flex-container-wrap-items($flex-basis, $max-expected-width: 2000px) {
display: flex;
flex-wrap: wrap;

> * {
max-width: 100%;
flex-grow: 1;
flex-basis: $flex-basis;
}

$multiplier: 1;
$current-width: 0px;

@while $current-width < $max-expected-width {
$current-width: $current-width + $flex-basis;
$multiplier: $multiplier + 1;

&[min-width~="#{$flex-basis * $multiplier}"] > * {
max-width: percentage(1/$multiplier);
}
}
}

Explanation:

Let's say, as per the OP's example, we want each item to have a maximum width of 100px, so we know that for a browser width of 100px we can fit one item per row, and so on:

| Viewport Width | Max Item Count Per Row | Item Width (min-max) |
|----------------|------------------------|----------------------|
| <= 100 | 1 | 0px - 100px |
| <= 200 | 2 | 50px - 100px |
| <= 300 | 3 | 50px - 100px |
| <= 400 | 4 | 50px - 100px |
| <= 500 | 5 | 50px - 100px |
| <= 600 | 6 | 50px - 100px |

We can write media queries to create the following rules:

| Viewport Width | Max Item Count Per Row | Item Max Width | Calculation |
|------------------------------------------------------------------------|
| <= 100px | 1 | 100% | (100/1) |
| <= 200px | 2 | 50% | (100/2) |
| <= 300px | 3 | 33.33333% | (100/3) |
| <= 400px | 4 | 25% | (100/4) |
| <= 500px | 5 | 20% | (100/5) |
| <= 600px | 6 | 16.66666% | (100/6) |

Like this:

li {
flex: 1 0 0
max-width: 100%;
}

@media(min-width: 200px) {
li { max-width: 50%; }
}

@media(min-width: 300px) {
li { max-width: 33.33333%; }
}

@media(min-width: 400px) {
li { max-width: 25%; }
}

@media(min-width: 500px) {
li { max-width: 20%; }
}

@media(min-width: 600px) {
li { max-width: 16.66666%; }
}

Of course, that's repetitive, but most likely you're using some sort of preprocessor, which can take care of the repetitiveness for you. That's precisely what the mixin in the above TL;DR section does.

All we have to do now is to specify 100px as our flex-basis, and optionally the maximum browser window width (defaults to 2000px) to create the media queries for:

@include flex-wrap-fix(100px)

Example

Finally, a forked version of the original CodePen example with the desired output, using the above mixin:

http://codepen.io/anon/pen/aNVzoJ

demo of the solution

Flex: wrapped items with same width as the rest

Add a few extra flex items and make them "invisible" by setting their height/padding/border to 0/none.
Based on how many columns you'll need, it takes one less for it to work.

* {  box-sizing: border-box;}
.wrapper { border: 1px solid red; width: 100%; max-width: 800px;}
ul { list-style: none; margin: 0; padding: 0;
display: flex; flex-wrap: wrap; margin: -1em -1em 0 0;}
li { border: 1px solid black; padding: 1em; margin: 1em 1em 0 0; flex: 1 1 10em;}
li:nth-last-child(-n+3) { height: 0; border: none; padding: 0; margin: 0 1em 0 0;}
<!DOCTYPE html><html>
<head> <link rel="stylesheet" href="style.css"> <script src="script.js"></script> </head>
<body> <div class="wrapper"> <ul> <li>Item 1</li> <li>Item 2</li> <li>Item 3</li> <li>Item 4</li> <li>Item 5</li> <li>Item 6</li> <li>Item 7</li> <li>Item 8</li> <li></li> <li></li> <li></li> </ul> </div> </body>
</html>

CSS Flex, last two items aren't the same width as the rest

As already mentioned in the comments, the behavior you described is because you set the flex property of your flex items to:

flex: 1 0 25%;

which is short for:

flex-grow: 1;
flex-shrink: 0;
flex-basis: 25%;

This means, that all of your flex-items have a width basis of 25% of the flex-container, while they cannot be smaller (no-shrink) and instead take all of the remaining space because of flex-grow: 1;

And because of that your flex-items will behave as follows:

1 flex-item will take 100% of the flex-containers width,

2 flex-items will each take 50% of the flex-containers width,

3 flex-items will each take 33.3333% of the flex-containers witdh and so on.

This also happens to apply for each row when you have flex-wrap: wrap;

If you want each row to have 3 items with a width of roughly 1/3, I suggest you try to set the flex property of your flex items to:

flex: 0 0 33.3333%

CodePen: https://codepen.io/anon/pen/jeXMBK?editors=1100#0

Alternatively you could use CSS-Grid which is the better choice most of the time, if you are dealing with 2-dimensional layouts.

Edit: Added CodePen sample

How to make all items the same width in a wrappable flexbox?

I want all items to have the same width, so I applied the following CSS attributes: flex-basis: 0 and flex-grow: 1.

That's a wrong approach. With flex-grow you are distributing free space in the container. Since the length of the content is different in each item, the free space will be different in each line. Hence, the columns won't align across rows, despite each item having flex-basis: 0.

With that, each item in every column should have had the same width, independently from its original width.

Actually, per the info I posted above, "With that, each item in every row should have had the same width, independently from its original width."

ul {  display: flex;  flex-wrap: wrap;  margin: 0;  padding: 0;}
li { flex-basis: 0; flex-grow: 1; white-space: nowrap; list-style-type: none; border: 1px dashed red;}
<ul>  <li>Long name</li>  <li>Long name</li>  <li>Short name</li>  <li>Short name</li>  <li>Short name</li>  <li>Short name</li>  <li>Short name</li>  <li>Long name</li>  <li>Long long long name</li>  <li>Long name</li>  <li>Long name</li>  <li>Long long long name</li>  <li>Long long long name</li>  <li>Long long long name</li>  <li>Long long long name</li>  <li>Short name</li>  <li>Long name</li>  <li>Long long long name</li>  <li>Long long long name</li>  <li>Long name</li>  <li>Short name</li></ul>

Equal width flex items even after they wrap

Currently, flexbox offers no clean solution for aligning flexible items in the last row or column. It's beyond the scope of the current spec.

Here's more information and various solutions people have used to get around the problem:

  • Targeting flex items on the last row
  • Is it possible for flex items to align tightly to the items above them?

However, last-row alignment is not a problem with another CSS3 technology, Grid Layout. In fact, it's very simple with this method (and requires no changes to the HTML):

.container {  display: grid;  grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));  grid-auto-rows: 20px;  grid-gap: 5px;}
.item { background: yellow; text-align: center; border: 1px solid red;}
<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 class="item">8</div>  <div class="item">9</div>  <div class="item">10</div></div>

How to make Flexbox items the same size

Set them so that their flex-basis is 0 (so all elements have the same starting point), and allow them to grow:

flex: 1 1 0px;

Your IDE or linter might mention that the unit of measure 'px' is redundant. If you leave it out (like: flex: 1 1 0), IE will not render this correctly. So the px is required to support Internet Explorer, as mentioned in the comments by @fabb;

Make flex items have equal width in a row

Flexbox method

In order to make the text items (.section-child) equal width, you need to use flex: 1 1 0, which you have done. This is the same as saying flex: 1.

However, this by itself doesn't achieve the goal for two reasons:

  1. The parent of .section-child, a flex container, but also a flex item in a larger container, is limited to the width of its content, by default. So it won't expand and the text can overflow the container. You need to apply flex: 1 to .section, as well.

  2. A flex item cannot be smaller than the size of its content, by default. The initial setting is min-width: auto. So flex: 1 cannot work to equally distribute container space, because a flex item cannot shrink past the longest item. You need to override this behavior with min-width: 0.

.top-level {  display: flex;  flex-flow: row wrap;}
.section { display: flex; flex-flow: row nowrap; border: 1px solid; margin-right: 12px; margin-top: 12px; flex: 1; min-width: 0;}
.section-child { display: flex; flex-flow: column nowrap; align-items: center; flex: 1; min-width: 0;}
.child-title { white-space: nowrap;}
.vertical-separator { width: 1px; background-color: rgba(0, 0, 0, 0.3); margin: 8px;}
<div class="top-level">  <section class="section">    <div class="section-child">      <h4 class="child-title">Title</h4>      <!--A lot more content here-->    </div>    <div class="vertical-separator"></div>    <div class="section-child">      <h4 class="child-title">Longer title</h4>      <!--A lot more content here-->    </div>    <div class="vertical-separator"></div>    <div class="section-child">      <h4 class="child-title">Much much longer title</h4>      <!--A lot more content here-->    </div>  </section>  <section class="section">    <div class="section-child">      <h4 class="child-title">Title</h4>      <!--A lot more content here-->    </div>    <div class="vertical-separator"></div>    <div class="section-child">      <h4 class="child-title">Longer title</h4>      <!--A lot more content here-->    </div>    <div class="vertical-separator"></div>    <div class="section-child">      <h4 class="child-title">Much much longer title</h4>      <!--A lot more content here-->    </div>  </section>  <section class="section">    <div class="section-child">      <h4 class="child-title">Title</h4>      <!--A lot more content here-->    </div>    <div class="vertical-separator"></div>    <div class="section-child">      <h4 class="child-title">Longer title</h4>      <!--A lot more content here-->    </div>    <div class="vertical-separator"></div>    <div class="section-child">      <h4 class="child-title">Much much longer title</h4>      <!--A lot more content here-->    </div>  </section></div>

CSS Flex grid - Same width for last item

Using a flex box you have to be careful about your max- and min- widths. Quoting from w3schools:

Tip: Elements that are flexible can shrink or grow as the box shrinks and grows. Whenever there is extra space in a box, flexible elements are expanded to fill that space.

To counteract this, initiate a width of X number pixels for your container but use percentages instead of your pixel amounts for the interior boxes. The percentage of the container will be the same for all your columns.

Doing this also removes the need for a flex box because those are only if you want expansion of the boxes in the horizontal direction (making them wider based on content of each one) rather than vertical direction.

PS - if you want this responsive and to keep the widths proportional to the space in between as well, use percentages for your margins as well (but keep in mind border and padding sizes). This isn't necessary but is a useful feature for keeping a consistent look and feel on different devices.

Same width flex items except for the last one

.child {
flex: 0 0 calc(33.33% - 10px);
}

.child:last-child {
flex-grow: 1;
}

revised codepen

Every child has the same width: 33.33% less the horizontal margins.

The last child will have the same width and consume remaining space in the row, if any.



Related Topics



Leave a reply



Submit