Make grid container fill columns not rows
Asked Answered
G

6

180

I want my grid to fill in vertically like this:

1 4 7 
2 5 8
3 6 9
... arbitrary number of additional rows.

Instead, it fills in horizontally like this:

1 2 3
4 5 6
7 8 9

I want to specify the number of columns in my grid, not the number of rows.

This is what my div looks like with inline CSS styling:

<div style="display:grid; grid-template-columns:1fr 1fr 1fr;">
  <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>

It's important that my grid be 3 columns wide, but I want the items to be populated by column, not by row. Is this possible in CSS Grid? I've read through this https://css-tricks.com/snippets/css/complete-guide-grid/ but didn't see anything about order.

CSS Flexbox has flex-direction, isn't there an attribute like that for CSS Grid?

Gyneco answered 21/5, 2017 at 2:15 Comment(0)
H
136

For a vertically-flowing grid that creates new columns as necessary, and rows are not defined, consider using CSS Multi-Column Layout (example). CSS Grid Layout (at least the current implementation - Level 1) cannot perform this task. Here's the problem:

In CSS Grid Layout, there is an inverse relationship between the grid-auto-flow and grid-template-rows / grid-template-columns properties.

More specifically, with grid-auto-flow: row (the default setting) and grid-template-columns both defined, grid items flow nicely in a horizontal direction, automatically creating new rows as necessary. This concept is illustrated in the code in the question.

#container {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  grid-auto-flow: row;
}
<div id="container">
  <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>

However, with a switch to grid-template-rows, grid items stack in a single column.

#container {
  display: grid;
  grid-template-rows: 1fr 1fr 1fr;
  grid-auto-flow: row;
}
<div id="container">
  <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>

There is no automatic creation of columns with grid-auto-flow: row and grid-template-rows. grid-template-columns must be defined (hence, the inverse relationship with grid-auto-flow).

#container {
  display: grid;
  grid-template-rows: 1fr 1fr 1fr;
  grid-template-columns: 1fr 1fr 1fr;
  grid-auto-flow: row;
}
<div id="container">
  <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>

The same behavior is true in the reverse scenario.

With grid-auto-flow: column and grid-template-rows both defined, grid items flow nicely in a vertical direction, automatically creating new columns as necessary.

#container {
  display: grid;
  grid-template-rows: 1fr 1fr 1fr;
  grid-auto-flow: column;
}
<div id="container">
  <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>

However, with a switch to grid-template-columns, grid items stack in a single row. (This is the problem most people ask about, including in this question.)

#container {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  grid-auto-flow: column;
}
<div id="container">
  <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>

There is no automatic creation of rows. That requires grid-template-rows to be defined. (This is the solution most often provided, but it is usually rejected because the layouts have a variable number of rows.)

#container {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  grid-template-rows: 1fr 1fr 1fr;
  grid-auto-flow: column;
}
<div id="container">
  <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>

Hence, consider a multi-column layout solution, as suggested above.

Spec reference: 7.7. Automatic Placement: the grid-auto-flow property

Hansen answered 21/5, 2017 at 2:26 Comment(4)
Would love for that to work, but it just ends up on one line now: codepen.io/glenpierce/pen/XRyevJGyneco
I see what you're saying, I wasn't sufficiently clear. It's important that I have 3 columns.Gyneco
More background: I'm building a widget that's going to be used in several places. The containers can be any height and should be able to contain any number of rows. The only limit I'm trying to impose on the grid is that it be 3 columns wide and order the items by column first then row.Gyneco
Let us continue this discussion in chat.Gyneco
M
73

Another option is to drop CSS Grid and use CSS Columns, which does exactly what you ask and also have much better browser support.

.csscolumn {
  -webkit-column-count: 3;  /* Chrome, Safari, Opera */
  -moz-column-count: 3;     /* Firefox */
  column-count: 3;
}

/* styling for this demo */
.csscolumn {
  width: 50%;
}
.csscolumn + .csscolumn {
  margin-top: 10px;
  padding-top: 10px;
  border-top: 1px solid;
}
<div class="csscolumn">
  <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="csscolumn">
  <div>1</div>
  <div>2</div>
  <div>3</div>
  <div>4</div>
  <div>5</div>
</div>

<div class="csscolumn">
  <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>
Malinda answered 21/5, 2017 at 17:46 Comment(7)
Just as a small note: recent versions of Chrome (50+) and Firefox (52+) have already unprefixed column-related properties (developer.mozilla.org/en-US/docs/Web/CSS/…)Schramm
@IlyaStreltsyn I know, but as the amount of users that still use older versions is quite many, it is a good idea to keep the prefix a little bit longer :)¨Malinda
Absolutely right .... Most of the time the question shouldn't be how can i force this tool to behave like I want, but instead if I have choosen the right tool.Lynseylynus
@vals, agreed, unless of course, it's equally important for child elements to accept grid / flex properties.Hansen
Yeah, CSS Columns seem best suited for purely textual content rather than blocks, because of spotty wrapping behaviour that can break blocks apart unintentionally, with handling that differs cross-browser.Kristianson
this answer made me really happy right nowNudd
Additionally, the usage of break-inside: avoid-column; helps avoid child elements from breaking over multiple columns. See https://mcmap.net/q/92721/-how-to-prevent-column-break-within-an-elementCohune
V
23

The simplest method I've seen follows:

.grid {
	display: grid;
	grid-auto-flow: column;
	grid-gap: 1px;
	grid-template-columns: repeat(3, 1fr); 
	grid-template-rows: repeat(5, auto);    
}
<div class="grid">
<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>11</div>
<div>12</div>
<div>13</div>
</div>
Vanadium answered 9/1, 2019 at 20:57 Comment(2)
Simple, yes, but does not work if you need a variable number of rows, depending on the length of the incoming dataEnrollee
^ it does if you count the rows in JS and divide by the number of columns then round to the whole number and assign the CSS declaration in your JS file (what I ended up doing) --------------------- Thank you huge help!!Leaflet
L
9

More as a technical exercise than as a practical solution, you can get somehow your result using specific styles depending on the number of items

Let's see how it works:

.item:first-child:nth-last-child(n+4):nth-last-child(-n + 6) ~ .item:nth-child(n+3)

the first selector

.item:first-child:nth-last-child(n+4):nth-last-child(-n + 6)

is active is our list has between 4 and 6 elements. In this case, some item will be both in the first condition and in the second.

In this case, we want 2 items to be in the first column. target the remaining items (from the third onwards) with

~ .item:nth-child(n+3)

and put them on the second column. A similar rule, now for the 5th and onwards

~ .item:nth-child(n+5)

puts the other items in the third column. These 2 rules have the same precedence, and target both the last items, so it's critical that they appear in this order.

We need to repeat similar rules up to the maximum amount of items that can be present (probably a job for a preprocessor)

var elements = 5;

function add () {
    var ctn = document.getElementById("container");
    var ele = document.createElement("div");
    elements ++;
    ele.innerHTML = elements;
    ele.className = "item";
    ctn.appendChild (ele);
}
#container {
  width: 90%;
  border: solid 1px red;
  display: grid;
  grid-template-rows: 33% 33% 33%;
  grid-auto-flow: column dense;
}

.item {
  width: 90%;
  height: 80px;
  background-color: lightgreen;
  margin: 10px;
  grid-column: 1;
}

.item:first-child:nth-last-child(n+4):nth-last-child(-n + 6) ~ .item:nth-child(n+3) {
  background-color: yellow;
  grid-column: 2;
}

.item:first-child:nth-last-child(n+4):nth-last-child(-n + 6) ~ .item:nth-child(n+5) {
  background-color: tomato;
  grid-column: 3;
}

.item:first-child:nth-last-child(n+7):nth-last-child(-n + 9) ~ .item:nth-child(n+4) {
  background-color: burlywood;
  grid-column: 2;
}

.item:first-child:nth-last-child(n+7):nth-last-child(-n + 9) ~ .item:nth-child(n+7) {
  background-color: blueviolet;
  grid-column: 3;
}

.item:first-child:nth-last-child(n+10):nth-last-child(-n + 12) ~ .item:nth-child(n+5) {
  background-color: darkcyan;
  grid-column: 2;
}

.item:first-child:nth-last-child(n+10):nth-last-child(-n + 12) ~ .item:nth-child(n+9) {
  background-color: chartreuse;
  grid-column: 3;
}

.item:first-child:nth-last-child(n+13):nth-last-child(-n + 15) ~ .item:nth-child(n+6) {
  background-color: yellow;
  grid-column: 2;
}

.item:first-child:nth-last-child(n+13):nth-last-child(-n + 15) ~ .item:nth-child(n+11) {
  background-color: tomato;
  grid-column: 3;
}
<button onclick="add()">Add</button>
<div id="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>
Lynseylynus answered 21/5, 2017 at 6:54 Comment(0)
A
0

Here is one CSS Grid based approach using javascript and the CSSOM to insert a pair of:

transform: translate(x, y)

rules into a generated stylesheet.

The two transform rules (there are only two, based on the grid being 3 columns wide displace the lower elements of the original single column grid, moving the elements upwards and to the right.

Consequently, you can add any number of elements to the single-column grid, and the script will always adjust the grid so that it has three columns of more or less equal size.

If the columns cannot be of exactly equal size, then the taller column(s) will always be the first and / or second column (never the rightmost, third column).

Working Example (9 grid units):

var numberOfColumns = 3;

document.head.appendChild(document.createElement('style'));
var newStyles = document.styleSheets[(document.styleSheets.length - 1)];

var myGrid = document.getElementsByClassName('my-grid')[0];
var myGridUnits = myGrid.getElementsByTagName('div');

var tallColumn = Math.ceil(myGridUnits.length /  numberOfColumns);
var shortColumn = Math.floor(myGridUnits.length / numberOfColumns);

var nextUnit = 1;
var unitsRemaining = myGridUnits.length;
var xTranslate, yTranslate;
var columns = [];

for (var i = 0; i < (numberOfColumns - 1); i++) {


    if (unitsRemaining % shortColumn === 0) {
    
        columns.push(shortColumn);
    }

    else {

        columns.push(tallColumn);
    }
    
    nextUnit += columns[(columns.length - 1)];
    unitsRemaining -= columns[(columns.length - 1)];
    
    xTranslate = ((i + 1) * 48);
    yTranslate = 0;
    columns.forEach(function(columnHeight){yTranslate += (columnHeight * 48);});
                         
    newStyles.insertRule('.my-grid div:nth-of-type(n+' + nextUnit + ') {transform: translate(' + xTranslate + 'px, ' + (0 - (yTranslate)) + 'px);}', newStyles.cssRules.length);

}
.my-grid {
display: inline-grid;
grid-row-gap: 6px;
}

.my-grid div {
width: 40px;
height: 40px;
line-height: 40px;
text-align: center;
border: 1px solid rgb(127, 127, 127);
}
<div class="my-grid">
<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>

Working Example (10 grid units):

var numberOfColumns = 3;

document.head.appendChild(document.createElement('style'));

var newStyles = document.styleSheets[(document.styleSheets.length - 1)];

var myGrid = document.getElementsByClassName('my-grid')[0];
var myGridUnits = myGrid.getElementsByTagName('div');

var tallColumn = Math.ceil(myGridUnits.length /  numberOfColumns);
var shortColumn = Math.floor(myGridUnits.length / numberOfColumns);

var nextUnit = 1;
var unitsRemaining = myGridUnits.length;
var xTranslate, yTranslate;
var columns = [];

for (var i = 0; i < (numberOfColumns - 1); i++) {


    if (unitsRemaining % shortColumn === 0) {
    
        columns.push(shortColumn);
    }

    else {

        columns.push(tallColumn);
    }
    
    nextUnit += columns[(columns.length - 1)];
    unitsRemaining -= columns[(columns.length - 1)];
    
    xTranslate = ((i + 1) * 48);
    yTranslate = 0;
    columns.forEach(function(columnHeight){yTranslate += (columnHeight * 48);});
                         
    newStyles.insertRule('.my-grid div:nth-of-type(n+' + nextUnit + ') {transform: translate(' + xTranslate + 'px, ' + (0 - (yTranslate)) + 'px);}', newStyles.cssRules.length);

}
.my-grid {
display: inline-grid;
grid-row-gap: 6px;
}

.my-grid div {
width: 40px;
height: 40px;
line-height: 40px;
text-align: center;
border: 1px solid rgb(127, 127, 127);
}
<div class="my-grid">
<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>

Working Example (11 grid units):

var numberOfColumns = 3;

document.head.appendChild(document.createElement('style'));

var newStyles = document.styleSheets[(document.styleSheets.length - 1)];

var myGrid = document.getElementsByClassName('my-grid')[0];
var myGridUnits = myGrid.getElementsByTagName('div');

var tallColumn = Math.ceil(myGridUnits.length /  numberOfColumns);
var shortColumn = Math.floor(myGridUnits.length / numberOfColumns);

var nextUnit = 1;
var unitsRemaining = myGridUnits.length;
var xTranslate, yTranslate;
var columns = [];

for (var i = 0; i < (numberOfColumns - 1); i++) {


    if (unitsRemaining % shortColumn === 0) {
    
        columns.push(shortColumn);
    }

    else {

        columns.push(tallColumn);
    }
    
    nextUnit += columns[(columns.length - 1)];
    unitsRemaining -= columns[(columns.length - 1)];
    
    xTranslate = ((i + 1) * 48);
    yTranslate = 0;
    columns.forEach(function(columnHeight){yTranslate += (columnHeight * 48);});
                         
    newStyles.insertRule('.my-grid div:nth-of-type(n+' + nextUnit + ') {transform: translate(' + xTranslate + 'px, ' + (0 - (yTranslate)) + 'px);}', newStyles.cssRules.length);

}
.my-grid {
display: inline-grid;
grid-row-gap: 6px;
}

.my-grid div {
width: 40px;
height: 40px;
line-height: 40px;
text-align: center;
border: 1px solid rgb(127, 127, 127);
}
<div class="my-grid">
<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>11</div>
</div>

Working Example (14 grid units):

var numberOfColumns = 3;

document.head.appendChild(document.createElement('style'));

var newStyles = document.styleSheets[(document.styleSheets.length - 1)];

var myGrid = document.getElementsByClassName('my-grid')[0];
var myGridUnits = myGrid.getElementsByTagName('div');

var tallColumn = Math.ceil(myGridUnits.length /  numberOfColumns);
var shortColumn = Math.floor(myGridUnits.length / numberOfColumns);

var nextUnit = 1;
var unitsRemaining = myGridUnits.length;
var xTranslate, yTranslate;
var columns = [];

for (var i = 0; i < (numberOfColumns - 1); i++) {


    if (unitsRemaining % shortColumn === 0) {
    
        columns.push(shortColumn);
    }

    else {

        columns.push(tallColumn);
    }
    
    nextUnit += columns[(columns.length - 1)];
    unitsRemaining -= columns[(columns.length - 1)];
    
    xTranslate = ((i + 1) * 48);
    yTranslate = 0;
    columns.forEach(function(columnHeight){yTranslate += (columnHeight * 48);});
                         
    newStyles.insertRule('.my-grid div:nth-of-type(n+' + nextUnit + ') {transform: translate(' + xTranslate + 'px, ' + (0 - (yTranslate)) + 'px);}', newStyles.cssRules.length);

}
.my-grid {
display: inline-grid;
grid-row-gap: 6px;
}

.my-grid div {
width: 40px;
height: 40px;
line-height: 40px;
text-align: center;
border: 1px solid rgb(127, 127, 127);
}
<div class="my-grid">
<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>11</div>
<div>12</div>
<div>13</div>
<div>14</div>
</div>
Arvid answered 6/6, 2018 at 14:56 Comment(0)
S
-4

One way to fill the grid vertically is using column-count = 3; CSS property and removing the display: grid;. But in my case it caused an issue (big white space) on Iphone 13. Here is how I switched the order from horizontal to vertical in grid layout:

function horizontalToVerticalGridSorting(gridSelector) {
    const grid = document.querySelector(gridSelector);
    const gridItems = grid.children;
    const gridComputedStyle = window.getComputedStyle(grid);
    // Grid columns count
    const colCount = gridComputedStyle.gridTemplateColumns.split(' ').length;
    // Grid rows count
    const rowsCount = gridComputedStyle.gridTemplateRows.split(' ').length;
    // Total items count
    const itemsCount = gridItems.length;
    // Full columns amount
    const fullColCount = itemsCount % colCount || colCount;
    // Amount of items in full columns
    const fullColItems = fullColCount*rowsCount;
    // Current column
    let curX = 1;
    // Current row
    let curY = 1;
    for (let i = 0; i < itemsCount; i++) {
        gridItems[i].style.order = (curY - 1) * colCount + curX;
        // Number of items in column
        let perColItems = rowsCount;
        // Not full column
        if (i >= fullColItems) {
            perColItems--;
        }
        curY++;
        // Switching to the next column
        if (curY > perColItems) {
            curY = 1;
            curX++;
        }
    }
}
horizontalToVerticalGridSorting('.cols');
.cols {
  text-align: center;
  display: grid;
  grid-template-columns: repeat(4, 1fr);
}
.col {
  padding: 10px;
}
<div class="cols">
  <div class="col">1</div>
  <div class="col">2</div>
  <div class="col">3</div>
  <div class="col">4</div>
  <div class="col">5</div>
  <div class="col">6</div>
  <div class="col">7</div>
  <div class="col">8</div>
  <div class="col">9</div>
  <div class="col">10</div>
</div>

The function goes through all grid items and calculates the new coordinates [X, Y] for vertical sorting. Then the order is calculates based on that new coordinates. It works with any grid size

Stair answered 1/9, 2022 at 14:9 Comment(1)
columns is not display grid for many reasons, not least of which is the concept of text flow..Neath

© 2022 - 2024 — McMap. All rights reserved.