Can I order a grid of divs by column using flexbox properties?

Viewed 148

This is what I need to achieve: enter image description here (Note the empty grey slots)

Which in flexbox layout should normally behave like this: (if flex-wrap is set to wrap)

enter image description here

I know that this could be achieved with a CSS grid layout

.parent {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-template-rows: repeat(3, 1fr);
  grid-column-gap: 0px;
  grid-row-gap: 0px;
  height: 400px;
}

.parent div {
  border: 1px solid;
  box-sizing: border-box;
}

div:nth-child(1) { grid-area: 1 / 1 / 2 / 2; }
div:nth-child(2) { grid-area: 1 / 2 / 2 / 3; }
div:nth-child(3) { grid-area: 1 / 3 / 2 / 4; }
div:nth-child(4) { grid-area: 2 / 1 / 3 / 2; }
div:nth-child(5) { grid-area: 2 / 2 / 3 / 3; }
div:nth-child(6) { grid-area: 3 / 1 / 4 / 2; }
div:nth-child(7) { grid-area: 3 / 2 / 4 / 3; }
<div class="parent">
   <div></div>
   <div></div>
   <div></div>
   <div></div>
   <div></div>
   <div></div>
   <div></div> 
</div>

The thing is that I would like to use with justify-content: space-between or gap as in my scenario the child elements are narrower and not feeling so comfortable with css grid.

Example:

.parent {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
}

.parent div {
  border: 1px solid;
  box-sizing: border-box;
  width: calc(33% - 4px);
  height: 150px;
  margin-bottom: 8px;
}

/* just to illustrate: child(6) */
.parent div:nth-child(6) { transform: translate(calc(-100% - 8px), calc(100% + 8px)); }
<div class="parent">
   <div></div>
   <div></div>
   <div></div>
   <div></div>
   <div></div>
   <div></div>
   <div></div> 
</div>

Can this be done with flex?

There are 7 elements now but there will be more eventually, so this needs to be scaleable. Also, I'd rather not have to change the markup if possible.

4 Answers

Let me do a little summary of the request:

You have a flex container with 3 columns. When there are 3n + 1 items, the last 2 elements must be in the last row, leaving a gap in the previous row.

You can get this result using 2 pseudo elements as fillers (the second one could be skipped for some flex configurations). You need to use order to set them correctly, using some advanced nth-child selectors.

See in the snippet how this selectors trigger, as the background color shows.

The pseudo elements have a border of 0 pixels, You can change this for them to be visible.

.parent {
  display: inline-flex;
  flex-wrap: wrap;
  justify-content: space-between;
  border: solid 1px blue;
  margin: 5px;
  width: 100px;
}

.parent div {
  border: 1px solid;
  box-sizing: border-box;
  width: calc(33% - 4px);
  height: 30px;
  margin-bottom: 8px;
}

.parent div:nth-child(3n):nth-last-child(2) {
    background-color: yellow;
    order: 3;
}
.parent div:nth-child(3n+1):nth-last-child(1) {
    background-color: lightgreen;
    order: 3;
}

.parent:before {
  content: "";
  box-sizing: border-box;
  width: calc(33% - 4px);
  height: 0px;
  order: 2;
}


.parent:after {
  content: "";
  box-sizing: border-box;
  width: calc(33% - 4px);
  height: 0px;
  order: 4;
}

.parent:before, .parent:after {
  border: 0px solid red; /* change to 1px to show them */
}
<div class="parent">
   <div>1</div>
   <div>2</div>
   <div>3</div>
   <div>4</div>
   <div>5</div>
   <div>6</div>
   <div>7</div> 
</div>

<div class="parent">
   <div>1</div>
   <div>2</div>
   <div>3</div>
   <div>4</div>
   <div>5</div>
   <div>6</div>
   <div>7</div> 
   <div>8</div> 
</div>

<div class="parent">
   <div>1</div>
   <div>2</div>
   <div>3</div>
   <div>4</div>
   <div>5</div>
   <div>6</div>
   <div>7</div> 
   <div>8</div> 
   <div>9</div> 
</div>

<div class="parent">
   <div>1</div>
   <div>2</div>
   <div>3</div>
   <div>4</div>
   <div>5</div>
   <div>6</div>
   <div>7</div> 
   <div>8</div> 
   <div>9</div> 
   <div>10</div> 
</div>

writing-mode can wrap it downward in the desired shape

.parent {
  display: flex;
  flex-flow: wrap;
  width: 100%;
  writing-mode: vertical-lr;
}

and if needed you can order within

It wouldn't technically be semantically incorrect to use the <hr> element as a line-break for your flex children. It essentially acts as a placeholder, and will force the following flex children to a new line.

Using margin or padding with transform to try and break it to the next line may cause overlapping issues when resizing.

In CSS2 we were able to use break-after and break-before for these types of things. But it is not widely supported in CSS3.

I hope there is this sort of functionality in the future, but for now, CSS grid handles this type of design well.

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

.parent > div {
  background-color: #1b6b34;
  height: 100px;
  width: calc(100%/3);
}

.parent > div:nth-child(even) {
  background-color: #685219;
}

hr {
  border: transparent;
  width: .1px;
}

.parent > div:nth-child(6) {
  align-self: flex-end;
}
<div class="parent">
   <div></div>
   <div></div>
   <div></div>
   <div></div>
   <div></div><hr>
   <div></div>
   <div></div> 
</div>

I would rely on pseudo-elements and this can work up to 9 elements. I am adding some coloration to better understand the trick:

.parent {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
}

.parent div {
  border: 1px solid;
  box-sizing: border-box;
  width: calc(33% - 4px);
  height: 150px;
  margin-bottom: 8px;
}

.parent:before,
.parent:after {
  content: "";
  flex-basis: 100%;
  /* to illustrate */ border-bottom:2px solid red;
}

.parent :nth-child(-n + 5) {
  order:-1;
  /* to illustarte */ background: lightgreen;
}
.parent :nth-child(n + 8) {
  order:1;
  /* to illustarte */ background: lightblue;
}
<div class="parent">
  <div>1</div>
  <div>2</div>
  <div>3</div>
  <div>4</div>
  <div>5</div>
  <div>6</div>
  <div>7</div>
  <div>8</div>
  <div>9</div>
</div>

Related