In this article, we will use CSS Grid to build some common layouts. The goal is to combine modern CSS tricks to create such layouts with the smallest code possible. In the end, you will have a collection of CSS boilerplate that you can easily reuse in your projects.
Full-Screen Container
Let’s start with our first layout which is the most basic one: Having a full-screen container. There are already a lot of codes where you have those cascade height: 100%
to achieve such an effect or the use of height: 100vh
.
We are not going to do this. Our code is as simple as the following:
html {
display: grid;
min-height: 100%;
}
main {
height: 100%;
}
No cascading height declaration, no use of 100vh
, and it does work with the default body margin. Not only this, but the container can also grow if its content is bigger than the screen height. In other words, the above code will set the minimum height to be equal to the screen height.
Here is a demo to illustrate. You can toggle the visibility of the content inside main
to see how the height grows to fit with no overflow issue.
How does it work? What is the logic behind the code?
First, we make the html as big as the Viewport using min-height: 100%
and not height :100%
so it can grow if there is more content. Then, by using display: grid
we rely on the default stretch alignment that will make the body element fill all the width/height of the html element while respecting its default margin. This is very useful as we can easily have padding/margin without affecting the full-screen behavior.
Now that we have our body element stretched to fill all the space, we use height: 100%
on the main element to fill that same space and have a full screen container!
You are probably wondering why we are able to use height: 100%
when there is no explicit height on the body element. This is the magic part of using CSS Grid and the default stretch alignment.
From the specification you can read:
Note: Since formulas calculated using only definite sizes, such as the stretch fit formula, are also definite, the size of a grid item which is stretched is also considered definite.
The size of a stretched element is considered definite so a child element can safely use percentage height.
In addition to height: 100%
, we can also make the body a grid container, and the main element will stretch inside it. Two different codes for the same layout.
html {
display: grid;
min-height: 100%;
}
body {
display: grid;
}
Worth noting that such configuration allows us to easily have centered content which is also a starter layout commonly used.
Adding a Header and a Footer
Let’s add a footer and header to our previous layout and make sure the main content takes the remaining height. This is also a common layout where we want the footer to stick to the bottom of the page if we have small content.
For this, we will turn the body element into a grid layout with the following code:
html {
display: grid;
min-height: 100%;
}
body {
display: grid;
grid-template-rows: auto 1fr auto;
}
We define three rows where the middle one takes all the remaining height using 1fr
. As simple as that.
This configuration will push the footer out of view if you have a lot of content inside main
and you will get a scrollbar on the whole page.
We can have a different configuration where we move the scrollbar to the main element and keep the footer always visible.
Instead of using min-height
, we use height
on the html element. Then we add min-height: 0
to the body element which is the trickiest part here. CSS grid (and also Flexbox) define a default min-height
(and min-width
) to child items as we can read in the specification:
To provide a more reasonable default minimum size for grid items, the used value of its automatic minimum size in a given axis is the content-based minimum size if all of the following are true: It is not a scroll container It spans at least one track in that axis whose min track sizing function is auto If it spans more than one track in that axis, none of those tracks are flexible Otherwise, the automatic minimum size is zero, as usual.
I know, the above is not intuitive and difficult to understand but always keep in mind this trick. You may need to add min-height: 0
or min-width: 0
in some cases where you face a strange overflow issue or you want an element to collapse/shrink even if it has a lot of content.
Finally, we add overflow: auto
to the main element and we have another configuration where the footer is always visible and we can scroll the main content.
html {
display: grid;
height: 100%;
}
body {
display: grid;
min-height: 0;
grid-template-rows: auto 1fr auto;
}
main {
overflow: auto;
}
Worth noting that even using this configuration we can keep the default body margin. No more headaches trying to remove that extra margin coming from nowhere!
Adding a Sidebar
Now let’s add a sidebar to our layout. In addition to the three rows, we will have two columns.
html {
display: grid;
height: 100%; /* OR min-height: 100% */
}
body {
display: grid;
min-height: 0;
grid-template-rows: auto 1fr auto;
grid-template-columns: 200px 1fr; /* 2 columns */
}
header,
footer {
grid-column: 1/-1; /* header and footer takes both columns */
}
main {
overflow: auto;
}
I used 200px
to set the width of the sidebar but you can also use a percentage value or even auto
.
We need to make the header and the footer span both columns using grid-column: 1/-1
which means starting at the first column and ending at the last one.
We can also create a sticky sidebar if you are using the configuration where we have the scrollbar on the whole page (min-height
instead of height
)
It’s important to notice the usage of align-self: start
to disable the default stretch alignment and allow the sidebar to have a sticky behavior:
aside {
position: sticky;
top: 8px;
align-self: start;
}
Dynamic Sidebar
What if the sidebar was optional? What if we want a right sidebar instead of a left one? We can write a conditional code that adjusts the CSS based on the HTML structure. We have 3 cases:
- The sidebar doesn’t exist.
- If the sidebar exists, it’s before the main element (left sidebar)
- If the sidebar exists, it’s after the main element (right sidebar)
For this we are going to use the :has()
selector.
html {
display: grid;
height: 100%; /* OR min-height: 100% */
}
body {
display: grid;
min-height: 0;
grid-template-rows: auto 1fr auto;
}
body:has(main + aside) {
grid-templat-columns: 1fr 200px;
}
body:has(aside + main) {
grid-templat-columns: 200px 1fr;
}
header,
footer {
grid-column: 1/-1;
}
main {
overflow: auto;
}
By default, we don’t define any columns (there is no sidebar). When the aside element exists and is placed after the main element body:has(main + aside)
we define two columns where the second one will have the width of the sidebar. When the aside element exists and is before the main element we do the opposite. We start by defining the width of the sidebar.
You can try all the combination and it works for all of them
The same can be done with the footer and header if you want to consider them as optional. I won’t give you the code, I let you try this alone as a small exercise to practice what we covered until now.
Equal height sections
We all wanted to have all those sections equal in height and more precisely equal to the tallest one among them. This can be done using one line of code:
body {
display: grid;
grid-auto-rows: 1fr;
}
That’s all!
All the sections are equal in height to the tallest one.
What about having equal height sections that are also full-screen? We can combine the code of the first layout with this last trick to achieve it
html {
display: grid;
height: 100%;
overflow: hidden; /* disable the scroll on the page */
}
body {
display: grid;
grid-auto-rows: 100%; /* all rows equal in height and take all the space */
overflow: auto; /* Let's have the scroll on the body element */
}
I am still keeping the default body margin to illustrate that the code works fine even with it but in most of the case you will want to remove it
I am also adding a touch of scroll snap in the last demo to enhance the scrolling experience.
We can make the code more generic and introduce a variable to control the number of visible sections at a time. All the sections are full height so we see only one but we can control this by changing the grid-auto-rows
like below:
body {
--n: 2; /* the number of sections */
--g: 8px; /* the gap */
gap: var(--g);
grid-auto-rows: calc((100% - (var(--n) - 1)*var(--g))/var(--n));
}
Intuitively we should use 100%/n
to define the height of the sections but we have to consider the gap in the calculation that’s why we need to remove n - 1
gaps from the total height before dividing by n
. When two sections are visible we will have one gap visible as well.
Vertical Layout
We can take the previous layout and turn it into a vertical one with simple changes:
html {
display: grid;
height: 100%;
overflow: hidden;
}
body {
--n: 2; /* the number of sections */
--g: 1em; /* the gap */
display: grid;
gap: var(--g);
grid-auto-flow: column; /* we define a column layout, the default was row */
/* We use "grid-auto-columns" instead of "grid-auto-rows" */
grid-auto-columns: calc((100% - (var(--n) - 1)*var(--g))/var(--n));
overflow: auto;
}
Another layout with almost the same code structure
We can also define rows and get another fancy layout
grid-template-rows: repeat(2,1fr);
The same can be done with the horizontal version. Go back to the code and try to do the same. Yes, another exercise for you!
Complex Layout
In this section, I won’t introduce a new layout but I will show you how easy is to combine all the previous code to create a more complex layout. We can have a header, a footer, an optional sidebar, scrollable sections inside the main element, and everything full screen.
I also added a few media queries to make things responsive and to illustrate how easily we can update the layout by adjusting a few values. I let you dig into the code and you will see that all it takes is a few variable changes to update the layout.
Responsive Grid of images
You probably already know this one but let’s add it to the collection since it’s a common layout.
body {
display: grid;
grid-template-columns: repeat(auto-fill,minmax(150px,1fr));
}
img {
aspect-ratio: 1; /* change this if you don't want square images */
width: 100%;
height: auto;
object-fit: cover;
}
In the above, I am using images with different sizes to illustrate that all of them will end up having the same size defined by the grid.
Conclusion
I hope you enjoyed this CSS Grid exploration of the different layouts. We started with a basic one and slowly went to a more complex one. The article ends here but here are more articles I wrote around CSS Grid if you want to explore more fancy layouts:
- Exploring CSS Grid’s Implicit Grid and Auto-Placement Powers
- Zooming Images in a Grid Layout
- CSS Grid and Custom Shapes
Now, it’s time for your to grab some codes and build your own layout!
Frequently Asked Questions
How can I create customizable layout options within my theme?
Use theme options or the Customizer API to allow users to customize layouts, colours and other design elements without directly editing code.
Can I use page builders to create layouts for my WooCommerce theme?
Use page builders, but choose one that is compatible and optimized for WooCommerce.
Are WordPress-free themes safe?
People often think that free themes have low quality. However, free WordPress themes actually have high quality and are free to use.
How can I create a theme options panel for users to customize the theme?
Utilize the WordPress Customizer API or create a separate options panel using a framework like Redux Framework, allowing users to customize colors, fonts, and other settings.
Temani Afif is an expert web developer, a content creator, and a CSS addict. He is the mastermind behind CSS Loaders, CSS Generators, CSS Tip and a lot of CSS stuff.
View all posts by Temani Afif