Center Items in First Row with CSS Grid
Imagine the following section on a website:
- A collection of elements, like a series of cards with marketing information, are presented in a grid display.
- The elements are arranged in rows of three.
- When there are an odd number of elements left over, they will be center-aligned horizontally.
There are a few ways to accomplish styling such a layout. Controlling Leftover Grid Items with Pseudo-selectors by Michelle Barker shares a clever CSS Grid solution. But here's a twist: What if the centered odd number of elements should appear in the first row instead of the last?
I've included a CodePen demo at the end of this article if you'd like to jump ahead. Otherwise, continue on a journey of style discovery.
Grid Stacks
Is it a trapezoid grid? Brick grid? Grid pyramid? Pyragrid? For the sake of this article, I ultimately picked a more generic name, calling it Grid Stack. Here's how we'll build a Grid Stack that contains five cards displayed in a three-column grid.
.grid-stack {
display: grid;
grid-template-columns: repeat(6, 1fr);
> * {
grid-column-end: span 2;
}
> :first-child {
grid-column-start: 2;
}
}
CSS nesting is being used here, which is newly supported across major browsers. Not feeling ready for that yet? We can move the nested rules into their own top-level rulesets. They just need to start with the parent selector name, i.e. .grid-stack > * { }
The parent grid-stack
container produces a template with six columns. Notice that the grid-template-columns
repeat count is double the amount of columns we want visually present in each row. Each child element will then span across two columns instead of one. Finally, the first child element is aligned to the start of the second column. The result is a visually centered top row.
Variations
So far, the styles we've created only apply when there are five cards positioned across three columns. We may want different variations depending on our designs. Three cards displayed in two columns? Seven cards in four? Let's tweak the above ruleset to utilize a CSS variable for the grid-template-columns
repeat count. Recall that this value should be twice the amount of expected columns.
.grid-stack {
display: grid;
grid-template-columns: repeat(calc(var(--columns) * 2), 1fr);
> * {
grid-column-end: span 2;
}
> :first-child {
grid-column-start: 2;
}
}
The --columns
value gets doubled as it passes through the CSS calc()
function. Now we're able to define the preferred amount of columns directly on the parent container.
<div class="grid-stack" style="--columns: 2"></div>
<div class="grid-stack" style="--columns: 3"></div>
<div class="grid-stack" style="--columns: 4"></div>
Demo
Bonus! Pyramid stacks
In the above demo, you may have discovered some configurations for the Grid Stack that result in a pyramid-style stack. Maybe now we can call it a pyragrid? Still not sure about that one... Anyway, to achieve this layout involves a few extra ingredients. We'll need to adjust the grid-column-start
position of the first element in each row. Let's jump right to the last example with the grid-stack-15
selector:
.grid-stack-15 {
--columns: 5;
> :first-child {
grid-column-start: 5;
}
> :nth-child(2) {
grid-column-start: 4;
}
> :nth-child(4) {
grid-column-start: 3;
}
> :nth-child(7) {
grid-column-start: 2;
}
}
- This calls for a five-column grid visually, so it sets
--columns: 5
. Recall that this value gets doubled and outputs a template of ten columns. - We'll nudge the first item in each row with
grid-column-start
. The top row element's start position is equal to the--column
value. Subsequent rules will decrease this value by 1.
It's surely possible to develop a Sass or PostCSS function that could dynamically generate this CSS output but that seemed a bit overkill for the demo. As an added bonus, check out Temani Afif's Stack Overflow answer that styles elements in a pyramid using float
and shape-outside
. Very cool!
Limitations
Each layout variation expects a specific odd-number of child elements to be rendered. I have explored ways of automatically adjusting the layout based on the element count but there were too many edge cases to consider. It created more problems than it solved. Additionally, while these layouts work nicely on a wider viewport, it may not fare as well where less space is available. A media
or container
query ruleset can ensure our content adapts appropriately, but it certainly couldn't be a one-size-fits-all conditional set of styles.
From the community
Updated on September 5th — Kevin Powell reached out on Mastodon and shared a CodePen Example that uses :has(:nth-child():last-child)
to result in this layout. I certainly dig the approach! Keep in mind that the same aforementioned limitations still apply.