3 columns grid (top to bottom) using grid CSS
Asked Answered
M

6

14

Ok, here's the situation, let's say I have a list with unknown number of items, it could be 10 or a 100, and I want to lay them out in 3 columns going top to bottom not left to right.

Right now I can achieve this using columns: 3; and column-gap: 10px; That's all fine and everything.

My question is, how to achieve the same results using display: grid; without knowing the number of items?

I know you can achieve this with CSS Grid if you have a fixed number of items, but is it possible with dynamic items? without using JS of course.

ul {
  list-style: none;
  columns: 3;
  column-gap: 10px;
}
<ul>
  <li>1</li>
  <li>2</li>
  <li>3</li>
  <li>4</li>
  <li>5</li>
  <li>6</li>
  <li>7</li>
  <li>8</li>
  <li>9</li>
  <li>10</li>
  <li>11</li>
  <li>12</li>
  <li>13</li>
  <li>14</li>
  <li>15</li>
  <li>16</li>
  <li>17</li>
  <li>18</li>
  <li>19</li>
  <li>20</li>
  <li>21</li>
  <li>22</li>
</ul>
Melba answered 5/6, 2018 at 6:51 Comment(1)
S
11

I don't think this is possible without previously know the number of items that you want to display, for your case you could do this:

ul {
   display: grid;
   grid-auto-flow: column;
   grid-template-rows: repeat(8, 1fr);
}
<ul>
  <li>1</li>
  <li>2</li>
  <li>3</li>
  <li>4</li>
  <li>5</li>
  <li>6</li>
  <li>7</li>
  <li>8</li>
  <li>9</li>
  <li>10</li>
  <li>11</li>
  <li>12</li>
  <li>13</li>
  <li>14</li>
  <li>15</li>
  <li>16</li>
  <li>17</li>
  <li>18</li>
  <li>19</li>
  <li>20</li>
  <li>21</li>
  <li>22</li>
</ul>

But the number of rows have to be defined previously.

Stamper answered 5/6, 2018 at 7:44 Comment(2)
I know that with fixed numbers of items it's easy, but I was asking about unknown number of items, that's the tricky part.Melba
Yes, just like I said, I don't think is possible only with css gridStamper
H
3

In theory, you can kind of achieve this with CSS Grid by using "Quantity queries" based on :nth-* selectors, like this:

ul {
  list-style: none;
  display: grid;
  grid-auto-flow: row dense;
  grid-template-columns: repeat(3, 1fr);
  grid-column-gap: 10px;
}

/* by default, items go in first column */
li { grid-column: 1; }

/* if there are 1 to 3 items, the 2nd one goes to 2nd column and the 3rd one goes to 3rd column */
li:first-child:nth-last-child(n + 1):nth-last-child(-n + 3) ~ li:nth-child(n + 2):nth-child(-n + 2) { grid-column: 2; }
li:first-child:nth-last-child(n + 1):nth-last-child(-n + 3) ~ li:nth-child(n + 3) { grid-column: 3; }

/* ... */

/* if there are 19 to 21 items, items 8-14 to 2nd column and items 15+ to 3rd one */
li:first-child:nth-last-child(n + 19):nth-last-child(-n + 21) ~ li:nth-child(n + 8):nth-child(-n + 14) { grid-column: 2; }
li:first-child:nth-last-child(n + 19):nth-last-child(-n + 21) ~ li:nth-child(n + 15) { grid-column: 3; }

/* if there are 22 to 24 items, items 9-16 to 2nd column and items 17+ to 3rd one */
li:first-child:nth-last-child(n + 22):nth-last-child(-n + 24) ~ li:nth-child(n + 9):nth-child(-n + 16) { grid-column: 2; }
li:first-child:nth-last-child(n + 22):nth-last-child(-n + 24) ~ li:nth-child(n + 17) { grid-column: 3; }

/* if there are 25 to 27 items, items 10-18 to 2nd column and items 19+ to 3rd one */
li:first-child:nth-last-child(n + 25):nth-last-child(-n + 27) ~ li:nth-child(n + 10):nth-child(-n + 18) { grid-column: 2; }
li:first-child:nth-last-child(n + 25):nth-last-child(-n + 27) ~ li:nth-child(n + 19) { grid-column: 3; }

/* and so on */
<ul>
  <li>1</li>
  <li>2</li>
  <li>3</li>
  <li>4</li>
  <li>5</li>
  <li>6</li>
  <li>7</li>
  <li>8</li>
  <li>9</li>
  <li>10</li>
  <li>11</li>
  <li>12</li>
  <li>13</li>
  <li>14</li>
  <li>15</li>
  <li>16</li>
  <li>17</li>
  <li>18</li>
  <li>19</li>
  <li>20</li>
  <li>21</li>
  <li>22</li>
</ul>

However, this approach isn't practical. In my opinion, the CSS Multi-column layout is the better solution here than CSS Grid.

Hube answered 5/6, 2018 at 8:50 Comment(1)
I agree that is quite the hassle and no practical, I guess I'll stick with Multi-columns layout.Melba
L
3

I have a list with unknown number of items, it could be 10 or a 100

As you already observed, CSS Multi-Column Layout produces the desired layout with pure CSS - without having to know in advance the number of items in the container.

This is not the case with CSS grids - you would have to know in advance the number of items in the grid in order to calculate the necessary number of rows - which is quite a limitation.

So I would suggest you stick with Multi-Column Layout for your layout.


Assuming the above limitation is ok - then you can create the layout with css as follows:

(Note: @Ander already answered this but for what it's worth here's a small explanation)

1) On the grid container change the grid-auto-flow property to column - This lays out the grid items vertically instead of horizontally (the default).

2) Once you know the number of items, you can calculate the number of rows necessary to create a balanced grid as follows:

#rows =  ceil( #items / #columns )

So for 22 items - the # rows = ceil(22/3) = 8

ul {
   display: grid;
   grid-auto-flow: column; /* 1 */
   grid-template-rows: repeat(8, 1fr);  /* 2 */
}

We can slightly improve this solution with SASS - to produce a more generic solution which calculates the number of rows. (SASS has a ceil function)

ul {
  list-style: none;
  padding: 0;
  display:grid;
  $num-items: 22;
  $num-cols: 3;
  $num-rows: ceil($num-items / $num-cols);
  grid-template-columns: repeat($num-cols, 1fr);
  grid-template-rows: repeat($num-rows, 1fr);
  grid-auto-flow: column;
  border: 5px solid gold;
}

Codepen Demo


FWIW: Here's an alternative solution which uses nth-child selectors without needing to change the grid-auto-flow property. Unfortunately it has the same limitation that the #items must be known in advance.

li:nth-child(-n + 24) {
  grid-column: 3;
}

li:nth-child(-n + 16) {
  grid-column: 2;
}

li:nth-child(-n + 8) {
  grid-column: 1;
}

SASS Codepen

Lepidote answered 5/6, 2018 at 9:57 Comment(0)
F
0

Try the following:

ul {
  list-style: none;
  columns: 3;
  -moz-column-count: 3;
  -moz-column-gap: 1.5rem;
  -moz-column-rule: none;
  -webkit-column-count: 3;
  -webkit-column-gap: 1.5rem;
  -webkit-column-rule: none;
  column-count: 3;
  column-gap: 1.5rem;
  column-rule: none;
}
<ul>
  <li>1</li>
  <li>2</li>
  <li>3</li>
  <li>4</li>
  <li>5</li>
  <li>6</li>
  <li>7</li>
  <li>8</li>
  <li>9</li>
  <li>10</li>
  <li>11</li>
  <li>12</li>
  <li>13</li>
  <li>14</li>
  <li>15</li>
  <li>16</li>
  <li>17</li>
  <li>18</li>
  <li>19</li>
  <li>20</li>
  <li>21</li>
  <li>22</li>
</ul>
Fidler answered 13/4, 2023 at 21:48 Comment(0)
R
0

The answer is simple if you know the number of items, columns and/or rows beforehand, so maybe the task at hand is to just identify the total number of items in your list.

ul {
  --column-count: 3;
  --total-items: 3;
  --row-count: calc(var(--total-items) / var(--column-count));
  display: grid;
  grid-auto-flow: column;
  grid-template-columns: repeat(var(--column-count), auto);
  grid-template-rows: repeat(var(--row-count), auto); /* Chrome seems to round up here automatically on its own */
}

There is a way to count the number of children using the :has selector, however if you ever expect to have a large number of items, then you'll need a large amount of CSS to account for it all. With that in mind, if you know the column count beforehand, then you can skip the numbers that don't matter, and rely on normal CSS inheritance rules to reduce the total size of your CSS.

/* 4 or more - assume up to 6 items and override "--total-items: 3" */
ul:has(> :nth-child(4)) {
  --total-items: 6;
}
/* 7 or more - assume up to 9 items and override "--total-items: 6" */
ul:has(> :nth-child(7)) {
  --total-items: 9;
}
ul:has(> :nth-child(10)) {
  --total-items: 12;
}
ul:has(> :nth-child(13)) {
  --total-items: 15;
}
ul:has(> :nth-child(16)) {
  --total-items: 18;
}
ul:has(> :nth-child(19)) {
  --total-items: 21;
}
/* ... add more as necessary. Maybe create a loop using sass. */

Of course, you can't account for an infinite number of items using this method, and even though most browsers fully support the :has selector, it is still relatively new, so using JavaScript will likely be more consistent. If you do decide to use JavaScript, you can set any of the following CSS properties to get the desired result in combination with the original CSS above: --total-items, --row-count, and/or grid-template-rows.

Rascally answered 10/1 at 3:21 Comment(0)
E
-1

I've used a little JavaScript here.Where 'lst' is id of the UL. I've used the CSS variables to pass the value of elements in the UL and ultimately calculating the required columns as per the requirement. Only downside being we will need to check if the total element number os odd or even.

<script>
 var list = document.getElementById("lst").childElementCount;
document.getElementById("lst").style.setProperty('--x',list/2);
</script>

<style>
    ul {
          list-style:none;
          display: grid;
          grid-auto-flow:column;
          grid-template-rows:repeat(var(--x),1fr);
         }
</style>
Epiphora answered 5/6, 2018 at 7:28 Comment(3)
This doesn't answer the question, the items have to be ordered from top to bottom on each column.Stamper
@UtkarshBais, The idea was not to use JS, I was asking if the same layout can be achieved with CSS Grid.Melba
@Melba I figured that, just tried achieving the result with minimal use of JavaScript. Might help someone with a simplar problem.Epiphora

© 2022 - 2024 — McMap. All rights reserved.