13

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>
Ruby
  • 2,207
  • 12
  • 42
  • 71

5 Answers5

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.

Ander
  • 758
  • 7
  • 15
  • 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. – Ruby Jun 05 '18 at 11:05
  • Yes, just like I said, I don't think is possible only with css grid – Ander Jun 05 '18 at 11:14
2

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.

Ilya Streltsyn
  • 13,076
  • 2
  • 37
  • 57
  • I agree that is quite the hassle and no practical, I guess I'll stick with Multi-columns layout. – Ruby Jun 05 '18 at 11:11
2

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

Danield
  • 121,619
  • 37
  • 226
  • 255
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>
Zach Jensz
  • 3,650
  • 5
  • 15
  • 30
Pete W
  • 1
-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>
Utkarsh Bais
  • 197
  • 8
  • This doesn't answer the question, the items have to be ordered from top to bottom on each column. – Ander Jun 05 '18 at 07:46
  • @UtkarshBais, The idea was not to use JS, I was asking if the same layout can be achieved with CSS Grid. – Ruby Jun 05 '18 at 11:14
  • @Ruby I figured that, just tried achieving the result with minimal use of JavaScript. Might help someone with a simplar problem. – Utkarsh Bais Jun 05 '18 at 11:17