'Build rows before columns with grid

I have a grid with a variable number of elements, but it should always have 3 columns:

<div class="grid grid-cols-3">
  <span>1</span>
  <span>2</span>
  <span>3</span>
  <span>4</span>
  <span>5</span>
  <span>6</span>
  <span>7</span>
  <span>8</span>
  <span>9</span>
  <span>10</span>
  <span>11</span>
  <span>12</span>
  <span>13</span>
</div>

With the above, I get the 3 columns, but the elements are placed into columns first, like so:

1   2   3
4   5   6
7   8   9
10  11  12
13

However, I want the elements to be put into rows first, like so:

1   6   11
2   7   12
3   8   13
4   9   
5   10 

Is this possible using only grid, without having to modify the classes since the number of elements varies? (And no JavaScript!)



Solution 1:[1]

Try with grid-rows-3 and grid-flow-col

span {
      border: 1px solid red;
}
<script src="https://cdn.tailwindcss.com"></script>
<div class="grid grid-rows-3 grid-flow-col">
  <span>1</span>
  <span>2</span>
  <span>3</span>
  <span>4</span>
  <span>5</span>
  <span>6</span>
  <span>7</span>
  <span>8</span>
  <span>9</span>

</div>

Solution 2:[2]

Use the Tailwind attributes grid-rows-3 with grid-flow-col to make the list like this:

<div class="grid grid-rows-3 grid-flow-col gap-4">
  <span>1</span>
  <span>2</span>
  <span>3</span>
  <span>4</span>
  <span>5</span>
  <span>6</span>
  <span>7</span>
  <span>8</span>
  <span>9</span>

</div>

Solution 3:[3]

The easiest way is to simply use CSS multi-column layout. The following demo does use JavaScript to allow the user to adjust the number of columns of, and to add more elements to, the content but otherwise the layout is simply CSS, relying on the line:

column-count: var(--columns, 1);

In which we specify that the number of columns must be equal to the CSS custom property of --columns (which the JavaScript allows you to adjust), or – if that property doesn't exist, or is invalid – or the default-value of 1.

The multi-column layout will also try to evenly-balance the column-heights.

The code, below, features explanatory code-comments to give insight into how this works:

// retrieves the first/only <input> element in the document of "type=number":
const input = document.querySelector('input[type=number]'),
  // retrieves the first/only element in the document with the class of "multicol":
  multicol = document.querySelector('.multicol');

// we set the value of the <input> to the value held in the current `--columns`
// custom property:
input.value = multicol.style.getPropertyValue('--columns');

// we select all <button> elements in the document, and iterate over that
// NodeList using NodeList.prototype.forEach():
document.querySelectorAll('button').forEach(
  // we use EventTarget.addEventListener() to bind an anonymous Arrow
  // function as the event-handler for the 'click' event on the <button>
  // element(s):
  (btn) => btn.addEventListener('click', () => {
    // creating a <span> element:
    let span = document.createElement('span'),
      // from the btn (<button>) element passed into the function-body
      // of the Arrow function we navigate to the parent-node of that
      // <button> element, and from there we find the '.multicol'
      // element:
      spanParent = btn.parentNode.querySelector('.multicol');
    // we then append the new <span> to that element:
    spanParent.append(span);
  })
);

// here we bind an anoymous Arrow function as the event-handler for a
// 'change' event on the <input>:
input.addEventListener('change', (e) => {
  // we get a reference to that <input>, using the Event.currentTarget()
  // property:
  let changed = e.currentTarget,
    // from there we navigate from the <input> to the closest <main>
    // element, and retrieve the first/only element with a class of
    // "multicol":
    columnElement = changed.closest('main').querySelector('.multicol');

  // we then use the CSSStyleDeclaration.setProperty() method, to update
  // the value of '--columns' custom CSS property to the current value
  // of the <input>:
  columnElement.style.setProperty('--columns', changed.value);
});
/* custom properties used through the stylesheet: */
:root {
  --columns: 1;
  --colGap: 0.5em;
  --rowGap: 0.25em;
}


/* a hugely naive, simple CSS reset/normalisation to
   ensure consistent base-styles for cross-browser
   normalisation: */
*,
::before,
::after {
  box-sizing: border-box;
  font-family: Roboto, Montserrat, system-ui;
  font-size: 16px;
  font-weight: 400;
  margin: 0;
  padding: 0;
}

main {
  display: grid;
  gap: 1em;
  margin-block: 1em;
  margin-inline: auto;
  /* setting an ideal width of 60vw (60 percent of the viewport-width,
     with a minimum size of 20em and a maximum size of 800px: */
  width: clamp(20em, 60vw, 800px);
}

h2 {
  font-size: 1.2em;
  font-weight: 600;
  text-align: start;
}

label,
div {
  border: 1px solid #000;
  padding: 0.5em;
}

label {
  /* a habit from positioning the label-text after the associated <input>
     in order to style that text based on status of the <input>, but not
     needed in this instance: */
  display: flex;
  flex-direction: row-reverse;
  gap: var(--colGap);
  align-items: center;
  justify-content: center;
}


/* garish, but it does ensure that people can see when the <input> is focused: */
label:focus-within {
  background-image: linear-gradient(45deg, hsl(0 100% 50% / 0.4), hsl(60 100% 50% / 0.6));
}

label input {
  padding-block: var(--rowGap);
  padding-inline-start: var(--colGap);
}

label span::after {
  content: ':';
}

main>div {
  margin: auto;
}

.multicol {
  counter-reset: spanCount;
  /* setting the number of columns in the element to be equal
     to the --columns custom-property, or a default-value of 1: */
  column-count: var(--columns, 1);
  column-gap: var(--colGap);
  max-width: 100%;
  width: 100%;
}

.multicol span {
  display: block;
  padding-block: var(--rowGap);
}

.multicol span:nth-child(odd) {
  background-color: palegreen;
}

.multicol span::before {
  counter-increment: spanCount;
  content: counter(spanCount);
}
<main>
  <h2>Custom columns:</h2>
  <label>
      <input type="number" min="1" step="1" value="1">
      <span>Set number of columns</span>
      </label>
  <button>Add new element</button>
  <div class="multicol" style="--columns: 3">
    <span></span>
    <span></span>
    <span></span>
    <span></span>
    <span></span>
    <span></span>
    <span></span>
    <span></span>
    <span></span>
  </div>
</main>

JS Fiddle demo.

References:

Bibliography:

Solution 4:[4]

This might be cumbersome, but you can change the visual order by order css property. (I hope someone can find to kinda automate the same approach with JS).

.grid-container {
  display: grid;
  grid-template-columns: repeat(3, 1fr); }
.grid-container span:nth-of-type(1) { order: 1; }
.grid-container span:nth-of-type(2) { order: 4; }
.grid-container span:nth-of-type(3) { order: 7; }
.grid-container span:nth-of-type(4) { order: 10; }
.grid-container span:nth-of-type(5) { order: 13; }
.grid-container span:nth-of-type(6) { order: 2; }
.grid-container span:nth-of-type(7) { order: 5; }
.grid-container span:nth-of-type(8) { order: 8; }
.grid-container span:nth-of-type(9) { order: 11; }
.grid-container span:nth-of-type(10) { order: 3; }
.grid-container span:nth-of-type(11) { order: 6; }
.grid-container span:nth-of-type(12) { order: 9; }
.grid-container span:nth-of-type(13) { order: 12; }
<div class="grid-container">
  <span>1</span>
  <span>2</span>
  <span>3</span>
  <span>4</span>
  <span>5</span>
  <span>6</span>
  <span>7</span>
  <span>8</span>
  <span>9</span>
  <span>10</span>
  <span>11</span>
  <span>12</span>
  <span>13</span>
</div>

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Evren
Solution 2
Solution 3 David Thomas
Solution 4 first user