'Split items into two columns CSS grid
Is there a way to achieve the following via CSS grid?
- If the number of items is odd, the first column should have 1 more row than the second column.
1 4
2 5
3
- If the number of items is even, the columns should have the same number of rows.
1 3
2 4
Tried using grid-template-rows
but the number of rows per column is fixed.
Solution 1:[1]
UPDATED: Added two more options how to handle it by HTML/CSS.
What about using grid
? There is 3 options, how it can be done:
- Using default item order;
- Using CSS variable, where you can define row number to break;
- Using additional class, added to child item, after wich it's siblinks will go to next column;
/* Option-1. Default */
.container {
display: grid;
grid-template-columns: repeat(2, minmax(50px, 1fr));
grid-gap: 1rem;
width: fit-content;
}
.container > div {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
background-color: var(--color, tomato);
aspect-ratio: 1/1;
}
/* End of Option-1. */
/* Option-2. With additional class */
.container--with-break {
grid-auto-flow: column;
}
.container--with-break > div {
grid-column: 1;
}
.container--with-break > .break ~ div {
grid-column: 2;
}
/* End of Option-2. */
/* Option-3. With CSS variable */
.container--with-break-row {
grid-auto-flow: column;
grid-template-rows: repeat(var(--break-row), 1fr);
}
/* End of Option-3. */
/* Example stylings */
.wrapper {
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-gap: 2rem;
align-items: start;
width: fit-content;
}
hr {
margin-top: 2rem;
margin-bottom: 2rem;
}
/* End of Ex. stylings */
<p>Option with default order</p>
<div class="wrapper">
<div class="container">
<div>1</div>
<div>2</div>
<div>3</div>
<div>4</div>
<div>5</div>
</div>
<div class="container">
<div>1</div>
<div>2</div>
<div>3</div>
<div>4</div>
</div>
</div>
<hr />
<p>Option with changing order. Using CSS variables</p>
<div
class="wrapper"
style="--color: mediumturquoise;"
>
<div
class="container container--with-break-row"
style="--break-row: 3;"
>
<div>1</div>
<div>2</div>
<div>3</div>
<div>4</div>
<div>5</div>
</div>
<div
class="container container--with-break-row"
style="--break-row: 2;"
>
<div>1</div>
<div>2</div>
<div>3</div>
<div>4</div>
</div>
<div
class="container container--with-break-row"
style="--break-row: 3;"
>
<div>1</div>
<div>2</div>
<div>3</div>
<div>4</div>
<div>5</div>
<div>6</div>
</div>
<div
class="container container--with-break-row"
style="--break-row: 4;"
>
<div>1</div>
<div>2</div>
<div>3</div>
<div>4</div>
<div>5</div>
<div>6</div>
<div>7</div>
</div>
</div>
<hr />
<p>Option with changing order. Using additional class to child item</p>
<div
class="wrapper"
style="--color: mediumpurple;"
>
<div class="container container--with-break">
<div>1</div>
<div>2</div>
<div class="break">3</div>
<div>4</div>
<div>5</div>
</div>
<div class="container container--with-break">
<div>1</div>
<div class="break">2</div>
<div>3</div>
<div>4</div>
</div>
</div>
Solution 2:[2]
If the number of items is always 4 or 5 you can do the following:
.container {
display: grid;
grid-gap: 1rem;
grid-auto-flow: column;
grid-template-rows: repeat(2, 1fr);
grid-auto-rows: 1fr;
justify-content: start;
margin: 10px;
border: 2px solid red;
}
.container>div {
background-color: lightblue;
padding: 20px;
}
/* create the extro row for the case of odd items */
.container>div:nth-child(3):nth-last-child(odd) {
grid-row: 3;
}
<div class="container">
<div>1</div>
<div>2</div>
<div>3</div>
<div>4</div>
<div>5</div>
</div>
<div class="container">
<div>1</div>
<div>2</div>
<div>3</div>
<div>4</div>
</div>
For the generic case like below
.container {
display: grid;
grid-gap: 1rem;
grid-template-rows: repeat(2, 1fr);
grid-auto-rows: 1fr;
grid-auto-flow: dense; /* you need this */
justify-content: start;
margin: 10px;
border: 2px solid red;
}
.container>div {
background-color: lightblue;
padding: 10px;
}
/* create the extro row for the case of odd items */
.container>div:nth-child(3):nth-last-child(odd) {
grid-row: 3;
}
/* all odd items counting from the end at the second row*/
.container > div:nth-last-child(odd) {
grid-row: 2;
}
/* the first and second item should never change*/
.container > div:first-child {
grid-row: 1;
}
.container > div:nth-child(2) {
grid-row: 2;
}
<div class="container">
<div>1</div>
<div>2</div>
<div>3</div>
<div>4</div>
<div>5</div>
</div>
<div class="container">
<div>1</div>
<div>2</div>
<div>3</div>
<div>4</div>
</div>
<div class="container">
<div>1</div>
<div>2</div>
<div>3</div>
<div>4</div>
<div>5</div>
<div>6</div>
</div>
<div class="container">
<div>1</div>
<div>2</div>
<div>3</div>
<div>4</div>
<div>5</div>
<div>6</div>
<div>7</div>
</div>
<div class="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>
<div class="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>10</div>
</div>
Solution 3:[3]
Credits for the grid go to @YaroslavTrach but the order of HTML elements can be achieved by splitting your elements into two arrays and then alternating between them when rendering.
const elements = [ 1, 2, 3, 4, 5 ];
const half = Math.ceil(elements.length / 2);
const firstHalf = elements.slice(0, half);
const secondHalf = elements.slice(-half + 1);
const toDiv = (i) => i ? `<div>${i}</div>` : ``;
document.getElementById('container').innerHTML = `
${firstHalf.map((el, id) => toDiv(el) + toDiv(secondHalf[id])).join('')}
`;
#container {
display: grid;
grid-template-columns: repeat(2, minmax(50px, 1fr));
grid-gap: 1rem;
width: fit-content;
}
#container>div {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
background-color: tomato;
aspect-ratio: 1/1;
}
<div id="container">
</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 | |
Solution 2 | |
Solution 3 |