9

I have a row of items of arbitrary width. They are centered within the container (note white space on the left and right sides of the red container):

enter image description here

Sometimes the container gets smaller than the width of all items:

enter image description here

When this happens, I want the items in the end to wrap to the next row like this:

enter image description here

It is very imporant for me that each row's content must be left-aligned, but the grid as a whole must be centered:

enter image description here

Initially, I tried implementing it with FlexBox. After a lot of frustration and hair pulling, I've learned that this is impossible witn FlexBox: https://stackoverflow.com/a/32811002/901944

Another answer on the same page suggests using CSS grid instead of flexbox.

CSS grid produces a slightly different result, but that also suits me:

enter image description here

Here's the code that makes it work:

.red-container {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(210px, max-content));
  justify-content: center;
}

This code contains a lot of keywords that I don't understand: grid-template-columns, repeat, auto-fit, minmax and max-content. I tried reading up on them and failed. None of guides and API docs explicitly explain how this particualr combination works. MDN docs are way too short and cryptic.

What I specifically struggle with is this 210px magic number. Why is it necessary? (Erm, I know it's necessary because how the spec is designed, but this does not help me understand.)

The sizes of items in my grid are arbitrary, so I can't use a fixed value. Also, setting this fixed value makes the result slightly off: small items grow and large items overflow the container.

What I essentially want is:

  grid-template-columns: repeat(auto-fit, minmax(min-content, max-content));

but that rule is recognized by browsers as faulty.

I've stumbled upon this answer that explains that using both min-content and max-content together is forbidden by the spec in this context. The answer's suggested solution is... to use Flexbox!

The loop has closed. I'm back to where I started, expect that I'm now lacking hair on my head for another round.

How to do I center my grid while left-aligning each row's content, with items having arbitrary widths?

Here's a boilerplate to fiddle with for your convenience: https://jsbin.com/vuguhoj/edit?html,css,output

The container can be resized by dragging it by the bottom-right corner.

PS No display: inline and float: left please.

.page {
  border: 1px solid black;
  overflow: hidden;
  resize: horizontal;
  max-width: 500px;
}

.grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(max-content, 50px));
  justify-content: center;
}

.item {
  border: 1px solid black;
  margin: 1px;
}
<div class="page">
  <div class="grid">
    <div class="item">
      Foofoofoo
    </div>
    <div class="item">
      Bar
    </div>
    <div class="item">
      BazBaz
    </div>
    <div class="item">
      QuuxQuuxQuux
    </div>
  </div>
</div>
Temani Afif
  • 245,468
  • 26
  • 309
  • 415
Andrey Mikhaylov - lolmaus
  • 23,107
  • 6
  • 84
  • 133
  • 1
    Will you accept an answer saying that this is impossible? This is most likely the case unless you are open to the use of JS – Temani Afif Jun 19 '20 at 19:37

6 Answers6

2

CSS grid approach-
root answer - joe82

.container {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(179px, max-content));
  grid-gap: 10px;
  justify-content: center;
  background: #999;
  overflow: hidden;
  padding: 10px;
}

.background {
  width: 179px;
  height: 64px;
  background: #99d9ea;
}

.background .child {
  border: 2px solid #000;
}

.background:nth-child(1) .child {
  width: 110px;
  height: 50px;
}

.background:nth-child(2) .child {
  width: 120px;
  height: 60px;
}

.background:nth-child(3) .child {
  width: 50px;
  height: 55px;
}

.background:nth-child(4) .child {
  width: 175px;
  height: 40px;
}
<div class="container">
    <div class="background"><div class="child"></div></div>
    <div class="background"><div class="child"></div></div>
    <div class="background"><div class="child"></div></div>
    <div class="background"><div class="child"></div></div>
</div>

Newbie here!

I admit that the above one isn't the output you desired, but it is my best attempt towards it, with the use of CSS grids. Here, you can understand that if we want to make it responsive then a minimum width is required after which, the column(content) will get carried to the next line, and that width is defined here(grid-template-columns: repeat(auto-fit, minmax(204px, max-content));) 204px. But because of it each column will take that much width at least, that's why I represented the actual dimension of a column with blue background and the actual content within the border.I just post it for your acknowledgment and approach so that you can get closer to the actual answer.

By the way, Flex Approach-
root idea - random COSMOS

.container {
  min-width: 130px;
  max-width: 340px;
  overflow: auto;
  background: #999;
  padding: 10px 40px;
  resize: horizontal;
  border: 1px solid #000;
}

.main-content {
  display: flex;
  flex-wrap: wrap;
  background: #fff;
  max-width: 340px;
  overflow: hidden;
}

.child {
  margin: 5px;
  padding: 5px;
  background: #99d9ea;
  border: 1px solid black;
}
<div class="container">
  <div class="main-content">
    <div class="child">Foofoofoo</div>
    <div class="child">Bar</div>
    <div class="child">BazBaz</div>
    <div class="child">QuuxQuuxQuux</div>
  </div>
</div>
Resize the window or the above div to see the results 

The above tells that the div is centered and the content is at left but not resizing according to content.

My personal opinion -

You should use @media for making it responsize, just as the way you want it to be, It is just like coding a lot for a simple output but it can give you the best and satisfying results out of your hard work and time!

Kindly inform me if you want me to make it responsize for you, I mean just like a demo-

Regard,
Om Chaudhary

Om_16
  • 696
  • 4
  • 13
1

If we go for center we don't get the left-align thing:

.page {
  border: 1px solid black;
  max-width: 500px;
  resize: horizontal;
  overflow: hidden;
  display: flex;
  justify-content: center;
  padding: 0 50px;
}

.grid {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
}

.item {
  border: 1px solid black;
  margin: 1px;
}
<div class="page">
  <div class="grid">
    <div class="item">Foofoofoo</div>
    <div class="item">Bar</div>
    <div class="item">BazBaz</div>
    <div class="item">QuuxQuuxQuux</div>
  </div>
</div>

Now with left-align (as in @Michael Benjamin answer) it doesn't do the center stuff:

.page {
  border: 1px solid black;
  max-width: 500px;
  resize: horizontal;
  overflow: hidden;
  display: flex;
  justify-content: center;
  padding: 0 50px;
}

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

.item {
  border: 1px solid black;
  margin: 1px;
}
<div class="page">
  <div class="grid">
    <div class="item">Foofoofoo</div>
    <div class="item">Bar</div>
    <div class="item">BazBaz</div>
    <div class="item">QuuxQuuxQuux</div>
  </div>
</div>

Why?

Because in the second code and in @Michael Benjamin code the .grid is technically centered. Visual: enter image description here

See the whole div is centered. The problem is that the .grid div doesn't change its width according to the content inside it. But, according to the width of its parent div (.page).

I know it doesnt solve your problem, but I am just trying to make sure that now you understand the main problem. So, maybe you can find the solution in another way.

Random COSMOS
  • 250
  • 4
  • 13
1

For this case, I see the only solution is to use JavaScript.

In this code we get each .item width. Then we set .flex width = total .item width. If the total .item width is smaller then the .page width - we set .flex width = ((total .item width) - (last .item width)). I hope the JS is pretty readable. If you need more explanation - can give it in comments.

Note that this is not wary flexible and universal solution, But it works good in this particular case, because it was written for it.

This snippet you can test only on browser window size change. Better to check in using Snippet Full page and Chrome console with Devise toolbar mode. Or here https://jsfiddle.net/focusstyle/sh6dnLvt/1/

window.addEventListener("resize", function() {
  flexWidth();  
});

function flexWidth() {
  let page = document.querySelector('.page');
  let flex = document.querySelector('.flex');
  let totalWidth = biggesWidth = itemWidth = lastWidth = 0;
  let flexItem = document.querySelectorAll('.item');
  for (let i = 0; i < flexItem.length; i += 1) {
    itemWidth = flexItem[i].offsetWidth;
    if (biggesWidth<itemWidth) {
      biggesWidth = itemWidth;
    } 
    biggesWidth = flexItem[i].offsetWidth;
    totalWidth += itemWidth;
    lastWidth = itemWidth; 
  }
  if (totalWidth > page.clientWidth) {
    totalWidth = totalWidth - lastWidth;
  }
  totalWidth += 1;  
  flex.style.cssText = "min-width: "+biggesWidth+"px; max-width: "+totalWidth+"px;"; 
}
.page {
  border: 1px solid black;
  overflow: hidden;
  text-align: center;
}

.flex {
  display: inline-flex;
  flex-wrap: wrap;
  justify-content: flex-start;
}

.item {
  border: 1px solid black;
}
<div class="page">
  <div class="flex">
    <div class="item">
      Foofoofoo
    </div>
    <div class="item">
      Bar
    </div>
    <div class="item">
      BazBaz
    </div>
    <div class="item">
      QuuxQuuxQuux
    </div>
  </div>
</div>
focus.style
  • 6,612
  • 4
  • 26
  • 38
1

Let's understand the 210px first. When you write the below code:

grid-template-columns: repeat(auto-fit, minmax(210px, max-content));

The browser knows when to wrap the items around. It ensures that your grid items will always be wider than 210px or at least equal to 210px.

If the browser has 420px width available, it will put 2 items in a row. If the browser has 630px width available, it will put 3 items in a row, and so on...

You can learn about CSS grids here

If you still don't want to have a min-content of 210px, you can always wite media queries in CSS.

Another thing that may suit your requirements is giving a min-width and max-width to your grid-items.

Hopefully, it saves some of your hair.

Kumar Sidharth
  • 578
  • 5
  • 13
  • Well, I did understand that part. What I don't understand is that why a fixed width is required by the spec. In other words, why can't it apply both automatic min width and automatic max width based on individual item widths? – Andrey Mikhaylov - lolmaus Jun 24 '20 at 07:12
0

You have two containers available – .page and .grid.

This enables you to distribute the two tasks – centering and left-alignment.

Use the top-level container for centering.

Use the nested container for wrapping and left alignment.

Here's a code concept:

.page {
  display: grid;
  grid-template-columns: 50px 1fr 50px;
  border: 1px solid black;
  max-width: 500px;
}

.grid {
  grid-column: 2;
  justify-self: center;
  display: flex;
  flex-wrap: wrap;
}

.item {
  border: 1px solid black;
  margin: 1px;
}
<div class="page">
  <div class="grid">
    <div class="item">Foofoofoo</div>
    <div class="item">Bar</div>
    <div class="item">BazBaz</div>
    <div class="item">QuuxQuuxQuux</div>
  </div>
</div>

jsFiddle demo

Michael Benjamin
  • 346,931
  • 104
  • 581
  • 701
-1

I don't know if I'm missing something but I think this is possible to achieve this with flexboxes.

You have to get this structure :

<div class="container">
  <div class="row">
    <div class="item item-1"></div>
    <div class="item item-2"></div>
    <div class="item item-3"></div>
    <div class="item item-4"></div>
  </div>
</div>

where the row has a max size and margin: 0 auto (to be centered)

Here is my example on Codepen

screen 1 screen 2

  • 1
    considering only your screenshot, this not the output the OP want. The blue background should be equal to content after the wrap (0 free space on both sides) – Temani Afif Jun 22 '20 at 14:10
  • It's not about the blue background. It's about centering the content. The distance from the left edge of the red container to the left child should be the same as the distance from teh right edge of the red container to the right child. – Andrey Mikhaylov - lolmaus Jun 23 '20 at 05:39