CSS Grid has been one of my favorite additions to the specification for years, but I recently realized I had not been utilizing one nifty feature enough: grid-template-areas
.
I was initially excited about the ability to delete several grid-column
and grid-row
declarations, but that alone does not warrant a blog article.
Something cool about template areas is that you can use different areas per breakpoint on the same element: you can add and omit areas in specific layouts.
Combine this knowledge with the magic of display: contents;
, and open the gates to layout heaven.
This article heavily relies on a CodePen example. Once you get to the CSS, please take a moment to read all the code comments for extra context.
Primer: Named grid template areas
You can use grid-template-areas
to create named CSS grid template areas (the name is very accurate, as you can see) instead of nameless rows and columns.
You can then assign elements to a grid area:
.container {
display: grid;
grid-template-areas:
"contents side-info"
} "footer footer";
main {
grid-area: contents;
}
aside {
grid-area: side-info;
}
footer {
grid-area: footer;
}
Note that you can supply multiple strings to grid-template-areas
; one string per row.
Each template area row needs to contain the same amount of columns. This may be an unpleasant surprise to those who were not good with fractions in maths class, but I believe in you!
Primer: display: contents;
Use display: contents;
to prevent an element from creating its display box. Instead, the element's shape will be defined by its pseudo-elements and children.
Weight, height, padding, and margin declarations will not affect elements with this type of display.
There are some caveats to this explanation, but for the scope of this article, it is all you need to know.
Five elements, three layouts
To demonstrate, we will create three different layouts with the same five HTML article elements, which we will refer to as "cards" from now on.
Layout 1 will be a fully vertical, stacked layout. This is a bit like cheating since you technically do not need display: grid;
to achieve it, given it is the default block flow.
(However, grid display gives you access to the gap
property, which makes it worth it to me, even in stacked layouts).
Layout 2 is a classic two-column layout. Cards 1–2 will be in the left column, and cards 3–5 will be in the right column.
Layout 3 consists of two rows. Cards 1–3 will be in the top row, and cards 4–5 in the bottom row.
For the sake of simplicity, we will not bother with different column widths and row heights.
Wrap it in a div
Think of layout 2 for a moment: it has two columns, with two rows in the left column and three in the right column.
We will wrap the three cards from the right column in a div to illustrate the magic of display: contents;
.
Do we need to wrap these cards in a div to achieve our desired layout? No, we do not in this scenario, but let us pretend we do—for science!
Fun fact: This technique is probably even more useful in flexbox than in grid for building layouts. Either way, it has its place for all kinds of styling convenience.
This is the HTML we end up with:
<div class="card-container">
<section class="cards">
<article class="card card--one">
<h2>Card 1</h2>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
</article>
<article class="card card--two">
<h2>Card 2</h2>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
</article>
<div class="cards-sidebar">
<article class="card card--three">
<h2>Card 3</h2>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
</article>
<article class="card card--four">
<h2>Card 4</h2>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
</article>
<article class="card card--five">
<h2>Card 5</h2>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
</article>
</div>
</section>
</div>
Time for some CSS magic
You can use traditional media queries, but I used container queries for this example; I named the container "cards":
.card-container {
container-name: cards;
container-type: inline-size;
}
Time to build the layouts.
Assigning grid areas
Each layout will use three or five areas out of a total of six assigned areas:
- Layouts 1 and 3 use
cards-one
tocards-five
. - Layout 2 uses
cards-one
,cards-two
, andcards-sidebar
.
We will assign all cards and the sidebar to an area by default, regardless of whether they exist in the current layout. This allows us to define these rules once and reuse them over all three layouts:
.card--one {
grid-area: cards-one;
}
.card--two {
grid-area: cards-two;
}
.card--three {
grid-area: cards-three;
}
.card--four {
grid-area: cards-four;
}
.card--five {
grid-area: cards-five;
}
& .cards-sidebar {
grid-area: cards-sidebar;
}
Creating the different layouts
From a mobile-first perspective, we will define the base grid and vertical layout as the default layout.
Next, use container queries to define the breakpoints for layouts 2 and 3.
Since we have already assigned each card and sidebar to a grid area globally, we do not need to update them per layout.
Instead, all we need to do is overwrite grid-template-areas
:
/* Base styles + layout 1 */
.cards {
display: grid;
gap: 1rem;
grid-auto-rows: 1fr;
grid-template-areas:
"cards-one"
"cards-two"
"cards-three"
"cards-four"
"cards-five";
}
/* Layout 2 */
@container cards (30rem <= width < 60rem) {
.cards {
grid-template-areas:
"cards-one cards-sidebar"
"cards-two cards-sidebar";
}
}
/* Layout 3 */
@container cards (width >= 60rem) {
.cards {
grid-template-areas:
"cards-one cards-one cards-two cards-two cards-three cards-three"
"cards-four cards-four cards-four cards-five cards-five cards-five";
}
}
(As someone who lived through the float and clearfix era of CSS, words cannot describe how much this simple code brings me joy.)
Override the sidebar display modes
If you have tried the code for yourself, you will have noticed that the layouts look a bit... off.
The sidebar, which groups cards 3 to 5, wrecks our grid. Since we do not want this sidebar in layouts 1 and 3, we use display: contents;
to remove its display box:
/* Base styles + layout 1 */
.cards-sidebar {
display: contents;
}
Layout 2 is still broken, so let us fix that next.
I love the CSS gap
property so much that it directly contributes to my extensive use of grid and flexbox. Let us stay true to that notion and slap some display: flex
on that sidebar:
/* Layout 2 */
@container cards (30rem <= width < 60rem) {
.cards-sidebar {
display: flex;
flex-direction: column;
gap: 1rem;
}
}
Merging the styles
Combine these rules, add a little stylistic fluff, and you will end up with:
See the Pen Conditional CSS grid template areas by Marijke Luttekes (@MHLut) on CodePen.
The result
This exercise results in a simple set of elements that transforms into three different layouts with very little CSS to boot:
See the Pen Conditional CSS grid template areas by Marijke Luttekes (@MHLut) on CodePen.
Wrap-up
Use named grid areas to avoid reassigning elements to (nameless) grid areas per layout.
Also, use display: contents;
to enable or disable wrapper elements whenever needed. This allows you to group related HTML elements when required and gives you more flexibility when you have no control over the HTML you need to style.
I am curious what you will achieve with these tricks!
Appendix: How did I figure this out?
I originally discovered this trick when rewriting my blog's layout.
The site's <header>
and <nav>
live together in a sidebar. The sidebar is only used for the two-column desktop layout, not the vertically stacked layout for mobile.
The blog has an optional sticky navigation option in settings, which pins the primary navigation to the top of the page. An important detail here is that the navigation within the sidebar div is below the header (which contains only a logo).
The sticky works something like this:
.navigation {
/* `inset-block-start` is the logical version of `top` */
inset-block-start: 0;
position: sticky;
}
Sticky navigation ceased to work on the vertical layout because the sidebar div prevented it from triggering the sticky positioning. I do not know precisely how that worked, which is fine.
Anyway, imagine my happy tears when, after a long session of messing around, I decided to give display: contents;
a go on the wrapper div. Lo and behold, the problem was solved, and feelings of relief ensued!