3

I'm using flex to create a multi column list that adjusts the number of columns based on the width of the container.

The problem I found is that if I want to use the full width of the parent by setting flex-grow to 1, the items in the last wrapped row are misaligned, since they try to fill the parent.

I found two workarounds that don't work for me: one used media queries, which I can't use because the parent is not the same width of the viewport; the other was using columns, but I can't use it because it causes issues with outlines being cut off and wrapped, which I have in my real setup.

Last two items' witdth is different

Q: Is there a way to make all items have the same width while filling the parent on full rows?

* {
  box-sizing: border-box;
}

.wrapper {
  border: 1px solid red;
  width: 50%;
}

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;
}
<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>
  </ul>
</div>

https://plnkr.co/edit/0u2QxcdLkDfhwV3zASrM

Resize until you get 2 items in the last row.

Michael Benjamin
  • 346,931
  • 104
  • 581
  • 701
QOI
  • 231
  • 3
  • 11

4 Answers4

7

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>

If all but the extra elements has a content, you could also use this CSS rule (thanks to Rick Hitchcock)

li:empty {
  height: 0;
  border: none;
  padding: 0;
  margin: 1em 1em 0 0;
}

If the amount of columns never will be more than 3, one can use pseudo elements. (Well, the columns can be more as long as there will never be more than 2 items missing on the last row)

* {
  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;
}

ul::before, ul::after {
  content: '';
  height: 0;
  border: none;
  padding: 0;
  margin: 0 1em 0 0;
  flex: 1 1 10em;
  order: 999;                /*  they need to be last  */
}
<!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>        
      </ul>
    </div>
  </body>

</html>
Asons
  • 84,923
  • 12
  • 110
  • 165
  • Wow so simple. I don't like having extra html items but it works perfectly. If I don't get a better answer I'll accept this one. – QOI Mar 13 '17 at 17:13
  • @QOI If you will not have more than 3 columns you can solve this using pseudo elements ... will update my answer with a sample – Asons Mar 13 '17 at 17:36
  • @QOI Updated with a pseudo sample – Asons Mar 13 '17 at 17:46
  • Thanks! I ended up using the `:empty` option, it's the most flexible in my case, since I expect to use the same style with different element widths across my webapp. – QOI Mar 14 '17 at 16:23
1

FLex will fail without a bit of javascript to fill or not the eventual space on last line.

Grid , in the futur will be 100% efficient for this kind of beahavior layout.

Snippet below that you can test in FF and Chrome if you have enable "experimental CSS".( https://igalia.github.io/css-grid-layout/enable.html )

* {
  box-sizing: border-box;
}

.wrapper {
  border: 1px solid red;
  width: 50%;
  min-width:12.25em;
}

ul {
  list-style: none;
  margin: 0;
  padding: 0 0 1em 1em;
  display: grid;
  grid-template-columns:    repeat(auto-fill, minmax(10em, 1fr))
}

li {
  border: 1px solid black;
  padding: 1em;
  margin: 1em 1em 0 0;
}
<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>
      </ul>
    </div>

Of course this is not the answer you expected, but it is about what CSS can (will) do.

You can keep on going with flex and a few tricks, but i believe the best would be flex + a bit of javascript :) at this time.


to start learning and testing the grid CSS system , see :

A tutorial https://css-tricks.com/snippets/css/complete-guide-grid/

browser supports http://caniuse.com/#search=grid

a Polyfill https://github.com/FremyCompany/css-grid-polyfill/

http://gridbyexample.com/browsers/

Community
  • 1
  • 1
G-Cyrillus
  • 101,410
  • 14
  • 105
  • 129
0

It depends on the actual situation, but you can prevent flex-grow for the last one, two, three (whatever, I made it for the last two) children with the appropriate selectors:

* {
  box-sizing: border-box;
}

.wrapper {
  border: 1px solid red;
  width: 50%;
}

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:last-child, li:nth-last-child(2) {
flex-grow: 0;
}
 
<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>
  </ul>
</div>
Johannes
  • 64,305
  • 18
  • 73
  • 130
  • This is not an option, the last two items won't be aligned with the ones on previous lines. I'd like them to grow/shrink, but in the same way as the previous elements. – QOI Mar 13 '17 at 17:03
  • 1
    @Johannes With some _ghost_ elements one can do that using CSS only, which I show in my answer – Asons Mar 13 '17 at 17:48
  • Actually, I would use inline-blocks or floats with percentage widths and media queries for them (number of items and percentage width depending on overall width), – Johannes Mar 13 '17 at 17:52
-1

It's like we need one more value in the "flex" rule for items: a boolean for "make all items uniform dimension". Like flex: grow shrink basis uniform

Ted Fitzpatrick
  • 910
  • 1
  • 8
  • 18