Adventures in subgrid
Posted 14 hours ago
I remember when I first learned about subgrid back in 2019. I created a Codepen to play around with it and was able to view it in Firefox (I think I had to enable something behind a flag). I was excited about the possibilities and problems that subgrid would allow us to solve. Subgrid is an addition to the original CSS Grid specification which allows us to extend the grid layout down through the DOM tree. (Subgrid was intended to be a part of the original CSS Grid implementation but needed some additional work to implement in the browser.)
But it took longer to get broad support of subgrid across browsers (it looks like it is part of Baseline 2023) and I sort of lost interest. There were plenty of other fun toys to play with like container queries. Subgrid fell off my radar and I took very little notice of when it was finally available in the four major evergreen browsers. But that changed recently.
A problem that subgrid solves
I think Josh Comeau put it very well in his recent article about subgrid:
“One of the hardest problems in CSS is when siblings need to be aware of each other inside nested / complex layouts. Subgrid offers a solution to these sorts of problems.”
Using a grid layout (without subgrid), all the items on the grid have to be siblings. And the children elements within the grid layout don’t have a way of being aware of the grid layout that their parents are a part of. Some developers have used display: contents as a work around but that solution has some negative accessibility impacts. Subgrid is the solution that we have needed and it doesn’t negatively affect accessibility.
So one of the main problems that I have solved with subgrid is being able to line up content across multiple columns. That is the problem I was solving with the very first Codepen that I played around with.
A typical layout for sites that I was building in 2019 were cards with a heading, description and a link or button. There was really no way to line those elements up over multiple columns when the headings could be one line or multiple lines and descriptions had a different number of lines. And I was asked many times during the QA process (especially by designers) to find solutions to line up these elements across the columns.
See the Pen Playing with Subgrid by Jeff Bridgforth (@webcraftsman) on CodePen.
Most of the time, I would opt to line up the “Read more” links or buttons by using a flex layout inside the card, with a flex-direction of column and a margin: auto on the top of the link or button. This would force those elements to the bottom of the card and they would line up across a grid row. This would provide a little bit nicer solution than if I didn’t use it. But I still was not able to achieve that alignment the heading, description/teaser, and read more button.
Recent solutions with subgrid
In several recents projects, I used subgrid to solve the very problem I have described above of having sibling content being aware of other sibling content so that they line up and share the same grid cell.
The Wonder App
I used subgrid as a solution in several places on a page promoting our Wonder App for Teen Girls. In the first example, I used subgrid to line up the descriptions below headings that varied from two lines to three lines. The four boxes use a parent grid on the container (an ordered list) and then each list item uses the subgrid to line up the content. The ordered list sits inside another grid layout for the whole section. I also used a grid to create some spacing around the secondary content list in the top right. I turned on the grid lines in devtools so you can see the different grids in action.

One of the gotchas of using subgrid is that you have to reserve space for multiple rows in your parent grid so that you can have the multiple grid rows on the subgrid. I learned this the hard way on one of the first solutions I tried with subgrid on the Revive Our Hearts site. If you don’t reserve the space for the rows, the content in each row will stack on top of each other in 1 row.

I ended up defining the number of rows in my CSS code.
.wonder-contents {
grid-column: 1/-1;
}
.wonder-contents ol {
--rows: 2;
display: grid;
grid-template-columns: 1fr;
column-gap: 36px;
row-gap: 36px;
list-style-type: '';
margin: 0;
padding: 0;
@media (min-width: calc((768 / 16) * 1em)) {
grid-template-columns: repeat(2, 1fr);
}
@media (min-width: calc((1300 / 16) * 1em)) {
grid-template-columns: repeat(4, 1fr);
}
}
.wonder-contents li {
background-color: var(--teal);
border-radius: 24px;
color: white;
display: grid;
grid-template-rows: subgrid;
grid-row: span var(--rows);
padding: 1em;
}
But what if you have a dynamic number of rows. Josh shared a more flexible solution in his article for when you have a dynamic number of rows.
.wonder-contents li {
grid-row: span 99;
}
You want to make your row span one that will exceed the actual number of rows you’ll. We’ll wind up with a 99-row grid, and all of the unused rows will stack up together at the bottom.
The one caveat with this solution is that you cannot use gap for the rows because then you would have 98 gaps in your subgrid because it will add the space even in your empty rows. But you could easily use a margin-block-start for all your first row to account for that.
Two columns of comparison
Another place that I used subgrid on the Wonder App page was further down the page where I had a comparison chart across two columns.

<div class="wonder-comparison">
<ul class="listing-with-dividers left-column">
<h2>Without Daily time in God’s word</h2>
<li>She stays discipled by culture instead of Christ.</li>
<li>Lies grow louder than truth.</li>
<li>Identity becomes fragile.</li>
<li>Her relationship with God stays shallow or stagnant.</li>
</ul>
<ul class="listing-with-dividers right-column js-animate" data-root-margin="-0px 0px -45% 0px">
<h2><span class="highlight"><svg>...</svg>With</span><span class="other-text">Daily time in God’s word</span>.</h2>
<li>She encounters Jesus daily.</li>
<li>She learns her identity is secure in Christ.</li>
<li>She builds deep-rooted faith habits.</li>
<li>She joins a global sisterhood anchored in God’s Word.</li>
</ul>
</div>
This one is very similar to the first example, just more rows.
.wonder-comparison {
border-bottom: 24px solid;
border-image: var(--wonder-border-color) 1;
color: var(--teal);
display: grid;
container-type: inline-size;
grid-template-columns: 1fr;
@media (min-width: calc((768 / 16) * 1em)) {
--rows: 5;
grid-template-columns: repeat(2, 1fr);
}
}
.wonder-comparison .left-column {
background-color: var(--teal);
}
.wonder-comparison .right-column {
background-image: url(../../images/wonder-app/gradient-background.jpg);
background-size: cover;
background-position: center;
}
.wonder-comparison h2 {
background-color: var(--cream);
font-family: var(----source-sans-pro);
font-size: 36px;
font-weight: 700;
text-transform: uppercase;
margin: 0;
padding-block-start: 5cqb;
padding-inline: 4cqi;
text-align: center;
@media (min-width: calc((768 / 16) * 1em)) and (max-width: calc((1199 / 16) * 1em)) {
padding-inline: 2cqi;
}
}
.wonder-comparison ul {
display: grid;
grid-template-rows: subgrid;
grid-row: span var(--rows);
padding: 10cqb 10cqi;
@media (min-width: calc((768 / 16) * 1em)) and (max-width: calc((1199 / 16) * 1em)) {
padding: 5cqb 5cqi;
}
}
.wonder-comparison li {
background-color: var(--cream);
border: 0;
align-content: center;
padding-inline: 4cqi;
position: relative;
text-align: center;
text-wrap: balance;
text-wrap: pretty;
text-transform: none;
@media (min-width: calc((768 / 16) * 1em)) and (max-width: calc((1199 / 16) * 1em)) {
padding-inline: 2cqi;
}
}
.wonder-comparison li:not(:last-child) {
&::after {
background-color: var(--teal);
bottom: -1px;
content: '';
height: 2px;
position: absolute;
left: 4cqi;
right: 4cqi;
z-index: 1;
}
}
.wonder-comparison li:last-child {
padding-block-end: 5cqb;
}
I didn’t use gap in this example because I wanted to add borders between the list elements. I decided to use a combination of padding and generated content (::after) to add in the borders. There is not currently a way to add borders with grid layout but the CSS Gap Decorations Module Level 1 will add that ability in the future. (See CSS Gap Decorations playground to see it in action.)
I also used container query units for padding and positioning of the borders. I like being able to set up proportional properties based on the containers that will change the value of the padding based on the container size.
Another example of lining up content across columns
Another place that I used subgrid to line up content across columns was on a year-end donation page. The headings had a different numbers of lines and the paragraphs were different sizes. Once again, subgrid was able to help me make the content look better because the content was able to share a common grid.

Going on from here
I am excited to explore more ways that I could utilize subgrid in the future. I liked a couple of examples from Josh’s article. The first one was of a layout with a a grid cell of a heading and a couple of paragraphs. The other cells were images of the portfolio that he introduced with the first cell of text content. He could have just the content cell and then six images but it made more sense to make the images into an unordered list. Subgrid then allows him to place the list items on the parent grid.

Later in the article, he has a grid of cards that contain an image and then text content. Without subgrid, he would not have been able to line up the images and make them the same size because he was using the fr unit. But without a relationship to the other siblings, the image size would be different in each card.

He also mentioned a couple of other gotchas to using subgrid besides reserving space for the rows which I mentioned above.
I am excited to find more solutions that subgrid unlocks. If you would like to learn more about subgrid, I would encourage you to read Josh’s article, Brand New Layouts with CSS Subgrid. He also mentioned that Kevin Powell has several helpful resources which I will list below.
Additional resources
- “Easy and more consistent layouts using subgrid” (Kevin Powell)
- “You can’t do this without subgrid” (Kevin Powell)
- “The dichotomy of grid” (Kevin Powell)
- I would also suggest one of Kevin’s recent videos, Why CSS Grid feels complex, and how to keep it simple that gives some good perspective about using CSS Grid for smaller solutions and not feel like you have to create the entire page layout with grid and a bunch of nested grids.
- Subgrid on MDN
- Grid by Example – Some excellent resources from Rachel Andrew to help you learn CSS Grid (does not cover subgrid)
Comment on this post