CSS Grid
CSS grid layout (from here on, just grid) is another, even more recent addition to CSS, continuing on from flexbox. While flex is a one-dimensional layout system—focused on horizontal or vertical arrangements—grid is a two-dimensional system, integrating the two directions together.
-
A complete guide to grid | CSS-Tricks
The grid version of the flexbox classic. -
CSS Grid Layout: A New Layout Module for the Web
WebKit (Safari’s) overview/introduction, from 2017. -
Basic concepts of grid layout | MDN
Back to MDN. -
Basics of CSS Grid
Web guru Jen Simmons breaks it down. -
Flexbox vs. CSS Grid
And also helps unpack when to use flex/grid. -
Grid by Example
Loads of layout examples. -
Grid Garden
Like the Froggy game, but for grid.
(We had some of this two-dimensionality with flex-wrap, but grid offers much more control.)
Grid is a lot like flex (this will be a running theme)—a display property applied on a parent/container element. This display: grid;
tells its (immediate) children/grid items how they should be laid out. Also like flex, there is display: inline-grid;
which behaves the same internally—but with the parent behaving as an inline element.
Grid supplants many of the previous box model layout systems (like floats, margin-centering, etc.) and, like flex, works much closer to how we think about layouts as designers. It can get complicated, but makes most layouts (especially responsive ones) much, much easier to implement.
There are many novel, powerful uses for grid—it is really the backbone of modern web layout. Let’s take a look.
Grid terminology
Grid introduces us to some new vocabulary:
Borrowed from the WebKit post grid-concepts
-
Line: The dividing lines that define the grid, vertical or horizontal. (Think gutters.)
-
Track: The horizontal or vertical space between the lines. (Think rows and columns.)
-
Cell: The intersection of a horizontal and vertical track. This is different from a grid item—the cell is the spot/placement, the item is the actual element—since as you’ll see, you can position items in an arbitrary cell.
-
Area: You can combine one or more adjacent grid cells into a rectangular area. Often you give these a name, for convenience.
New units and functions
Grid also introduces some specific new length units and functions to use them:
fr
This new unit represents a fraction of the available space in the grid container—usually, width. This is kind of like using percentages, except we no longer have to do the maths and any padding or gap (gutter) is already accounted for. This is very handy; you’ll use it a lot.
.two-thirds-one-third {
display: grid;
grid-template-columns: 2fr 1fr;
}
min-content
The intrinsic minimum width of an element. With text, this is the longest single word.
.narrow-sidebar {
display: grid;
grid-template-columns: 1fr min-content;
}
max-content
Same for the maximum. With text, this is the whole sentence/line.
.wider-sidebar {
display: grid;
grid-template-columns: 1fr max-content;
}
fit-content
A combo of the min/max. Uses available space—but never less than min-content
and never more than max-content
.
.fit-sidebar {
display: grid;
grid-template-columns: 1fr fit-content;
}
You can use these three values in grid properties, as we’ll see below—but they are also usable anywhere length units work—like width
.
minmax()
A function that defines a range for a track, setting a minimum and maximum length together. These are really useful for setting reasonable limits on responsive grid designs!
.flexible-sidebar {
display: grid;
grid-template-columns: 1fr minmax(200px, 400px);
}
repeat()
This function repeats a track list, so you don’t have to write it over and over.
.twelve-columns {
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr;
}
.also-twelve-columns {
display: grid;
grid-template-columns: repeat(12, 1fr); /* Much better. */
}
Whenever you are writing the same exact code over and over, there is probably a shorter way. Don’t repeat yourself!
Container (parent) properties
Again, grid is a lot like flex—primarily properties that are applied on a container/parent element.
grid-template-columns
/ grid-template-rows
Setting display: grid;
won’t do much until you also declare some columns or rows, with grid template. You can specify grid-template-columns
, grid-template-rows
, or both. These properties are followed by a track list of the size for each track:
section {
display: grid;
}
section:first-child {
grid-template-columns: 2fr 1fr;
}
section:nth-child(2),
section:nth-child(3) {
grid-template-rows: 2fr 1fr;
}
section:nth-child(3) {
grid-auto-flow: column;
}
section:last-child {
grid-template-columns: 2fr 1fr;
grid-template-rows: 2fr 1fr;
}
body {
font-family: sans-serif;
padding: 20px;
}
section {
background-color: gold;
}
section:not(:first-child) {
margin-top: 20px;
}
div {
background-color: tomato;
border-bottom: 2px solid firebrick;
border-right: 2px solid firebrick;
padding: 5px;
}
<section>
<div>
<p>Item 1</p>
</div>
<div>
<p>Item 2</p>
</div>
<div>
<p>Item 3</p>
</div>
</section>
<section>
<div>
<p>Item 1</p>
</div>
<div>
<p>Item 2</p>
</div>
<div>
<p>Item 3</p>
</div>
</section>
<section>
<div>
<p>Item 1</p>
</div>
<div>
<p>Item 2</p>
</div>
<div>
<p>Item 3</p>
</div>
</section>
<section>
<div>
<p>Item 1</p>
</div>
<div>
<p>Item 2</p>
</div>
<div>
<p>Item 3</p>
</div>
</section>
Notice in the second example, the items do not wrap to a new column—because grid-auto-flow: row;
is the default setting. The third example sets this to column
to make it flow to a new one.
Again like flex, there is similar behavior on the horizontal/vertical axes—with the defaults around horizontal/row based behavior since width is usually our constraint (with pages scrolling vertically).
So for many uses, you will only need to specify your column structure—leaving the rows to create themselves, as needed. This is called an implicit grid (vs. an explicit grid that we set/define):
section {
display: grid;
grid-template-columns: 2fr 1fr;
}
body {
font-family: sans-serif;
padding: 20px;
}
section {
background-color: gold;
}
div {
background-color: tomato;
border-bottom: 2px solid firebrick;
border-right: 2px solid firebrick;
padding: 5px;
}
<section>
<div>
<p>Item 1</p>
</div>
<div>
<p>Item 2 <br>with line <br>breaks</p>
</div>
<div>
<p>Item 3 <br>also</p>
</div>
<div>
<p>Item 4</p>
</div>
<div>
<p>Item 5</p>
</div>
</section>
</html>
The additional rows are automatically added, as needed. Note that they size vertically to their content.
grid-auto-columns
/ grid-auto-rows
By default, these implicit grid tracks are sized auto
(to their content), but you can also specify their size—often a height for the grid-auto-rows
:
section {
display: grid;
grid-auto-rows: 80px;
grid-template-columns: 2fr 1fr;
}
body {
font-family: sans-serif;
padding: 20px;
}
section {
background-color: gold;
}
div {
background-color: tomato;
border-bottom: 2px solid firebrick;
border-right: 2px solid firebrick;
padding: 5px;
}
<section>
<div>
<p>Item 1</p>
</div>
<div>
<p>Item 2 <br>with line <br>breaks</p>
</div>
<div>
<p>Item 3 <br>also</p>
</div>
<div>
<p>Item 4</p>
</div>
<div>
<p>Item 5</p>
</div>
</section>
</html>
But grid-auto-columns
only comes up if you force the columns to wrap with grid-auto-flow: column;
as in the earlier example. Again, height is usually not our main constraint, with scrolling!
gap
/ column-gap
/ row-gap
Grid also shares the gap
, column-gap
, and row-gap
properties with flex—to add gutters between the tracks. The syntax and behavior is the same!
section {
display: grid;
grid-template-columns: 2fr 1fr;
}
section:first-child {
gap: 10px;
}
section:nth-child(2) {
column-gap: 10px;
}
section:last-child {
row-gap: 10px;
}
body {
font-family: sans-serif;
padding: 20px;
}
section {
background-color: gold;
}
section:not(:first-child) {
margin-top: 20px;
}
div {
background-color: tomato;
border-bottom: 2px solid firebrick;
border-right: 2px solid firebrick;
padding: 5px;
}
<section>
<div>
<p>Item 1</p>
</div>
<div>
<p>Item 2 <br>with line <br>breaks</p>
</div>
<div>
<p>Item 3 <br>also</p>
</div>
<div>
<p>Item 4</p>
</div>
<div>
<p>Item 5</p>
</div>
</section>
<section>
<div>
<p>Item 1</p>
</div>
<div>
<p>Item 2 <br>with line <br>breaks</p>
</div>
<div>
<p>Item 3 <br>also</p>
</div>
<div>
<p>Item 4</p>
</div>
<div>
<p>Item 5</p>
</div>
</section>
<section>
<div>
<p>Item 1</p>
</div>
<div>
<p>Item 2 <br>with line <br>breaks</p>
</div>
<div>
<p>Item 3 <br>also</p>
</div>
<div>
<p>Item 4</p>
</div>
<div>
<p>Item 5</p>
</div>
</section>
justify-items
Also like flex (there’s a pattern here), we can position items within the tracks—but now we have control over both axes and the overall placement. To start, justify-items
positions all the grid items along their row axis.
The terminology here is always a bit confusing, but think of it this way—in grid, the main axis is always the horizontal row. So justify always means left/right, and align always means top/bottom. Easier to remember than flex!
section {
display: grid;
grid-template-columns: 2fr 1fr;
}
section:first-child {
justify-items: stretch; /* Default. */
}
section:nth-child(2) {
justify-items: start;
}
section:nth-child(3) {
justify-items: center;
}
section:last-child {
justify-items: end;
}
body {
font-family: sans-serif;
padding: 20px;
}
section {
background-color: gold;
}
section:not(:first-child) {
margin-top: 20px;
}
div {
background-color: tomato;
border-bottom: 2px solid firebrick;
border-right: 2px solid firebrick;
padding: 5px;
}
<section>
<div>
<p>Item 1</p>
</div>
<div>
<p>Item 2 <br>with line <br>breaks</p>
</div>
<div>
<p>Item 3 <br>also</p>
</div>
<div>
<p>Item 4</p>
</div>
<div>
<p>Item 5</p>
</div>
</section>
<section>
<div>
<p>Item 1</p>
</div>
<div>
<p>Item 2 <br>with line <br>breaks</p>
</div>
<div>
<p>Item 3 <br>also</p>
</div>
<div>
<p>Item 4</p>
</div>
<div>
<p>Item 5</p>
</div>
</section>
<section>
<div>
<p>Item 1</p>
</div>
<div>
<p>Item 2 <br>with line <br>breaks</p>
</div>
<div>
<p>Item 3 <br>also</p>
</div>
<div>
<p>Item 4</p>
</div>
<div>
<p>Item 5</p>
</div>
</section>
<section>
<div>
<p>Item 1</p>
</div>
<div>
<p>Item 2 <br>with line <br>breaks</p>
</div>
<div>
<p>Item 3 <br>also</p>
</div>
<div>
<p>Item 4</p>
</div>
<div>
<p>Item 5</p>
</div>
</section>
align-items
And align-items
directly corresponds to the flex values, to position all the items vertically along their column axis:
section {
display: grid;
grid-template-columns: 2fr 1fr;
}
section:first-child {
align-items: stretch; /* Default. */
}
section:nth-child(2) {
align-items: start;
}
section:nth-child(3) {
align-items: center;
}
section:last-child {
align-items: end;
}
body {
font-family: sans-serif;
padding: 20px;
}
section {
background-color: gold;
}
section:not(:first-child) {
margin-top: 20px;
}
div {
background-color: tomato;
border-bottom: 2px solid firebrick;
border-right: 2px solid firebrick;
padding: 5px;
}
<section>
<div>
<p>Item 1</p>
</div>
<div>
<p>Item 2 <br>with line <br>breaks</p>
</div>
<div>
<p>Item 3 <br>also</p>
</div>
<div>
<p>Item 4</p>
</div>
<div>
<p>Item 5</p>
</div>
</section>
<section>
<div>
<p>Item 1</p>
</div>
<div>
<p>Item 2 <br>with line <br>breaks</p>
</div>
<div>
<p>Item 3 <br>also</p>
</div>
<div>
<p>Item 4</p>
</div>
<div>
<p>Item 5</p>
</div>
</section>
<section>
<div>
<p>Item 1</p>
</div>
<div>
<p>Item 2 <br>with line <br>breaks</p>
</div>
<div>
<p>Item 3 <br>also</p>
</div>
<div>
<p>Item 4</p>
</div>
<div>
<p>Item 5</p>
</div>
</section>
<section>
<div>
<p>Item 1</p>
</div>
<div>
<p>Item 2 <br>with line <br>breaks</p>
</div>
<div>
<p>Item 3 <br>also</p>
</div>
<div>
<p>Item 4</p>
</div>
<div>
<p>Item 5</p>
</div>
</section>
Note that there isn’t any change on the last implicit row with the default auto
/content height.
There are also baseline align values, but I don’t think I’ve ever seen them used in grid—your layouts are often too nested/complicated for them to work.
justify-content
/ align-content
If the total size of your grid is less than the container (because of your explicit column or row sizes), you can set the overall justification and alignment within the container. Again, this is just like flex! Same syntax, same behavior—you get the idea. Grid is like Flex+.
Shorthand?
Grid also has shorthand properties for many of these, like grid
, grid-template
, place-items
, and place-content
. However, grid is complicated enough as it is! The shorthands really obfuscate the behavior, and aren’t worth the tighter syntax.
Okay, so this is mostly like flex! To the point where you can use them interchangeably for some layouts. You get it. But now let’s look at where grid offers more specific and powerful control.
Using repeat
Grid’s repeat
function is very commonly used to make even-column grids. And of course, they can be made responsive with media queries and CSS variables!
section {
--columns: 4;
display: grid;
grid-template-columns: repeat(var(--columns), 1fr);
}
@media (min-width: 400px) {
section { --columns: 6; }
}
@media (min-width: 600px) {
section { --columns: 8; }
}
body {
font-family: sans-serif;
padding: 20px;
}
section {
background-color: gold;
}
div {
background-color: tomato;
border-bottom: 2px solid firebrick;
border-right: 2px solid firebrick;
padding: 5px;
}
<section>
<div>
<p>Item 1</p>
</div>
<div>
<p>Item 2 <br>with line <br>breaks</p>
</div>
<div>
<p>Item 3 <br>also</p>
</div>
<div>
<p>Item 4</p>
</div>
<div>
<p>Item 5</p>
</div>
<div>
<p>Item 6</p>
</div>
<div>
<p>Item 7 with more text</p>
</div>
<div>
<p>Item 8</p>
</div>
<div>
<p>Item 9 longer</p>
</div>
<div>
<p>Item 10</p>
</div>
<div>
<p>Item 11</p>
</div>
</section>
Notice that the items always stick to the grid structure—independent of their content—unlike our previous flex-wrap
pseudo-grids.
Flex is sometimes referred to in this way as content-out, while grid is a layout-in system.
auto-fill
/ auto-fit
You can also use the repeat
function without specifying an exact number of columns, instead using auto-fill
or auto-fit
to automatically define your columns—making a grid inherently responsive without any media queries! These are great for controlling an even-column layout without much overhead:
section {
display: grid;
}
section:first-child {
/* Fill as many `100px` columns as possible. */
grid-template-columns: repeat(auto-fill, 100px);
}
section:nth-child(2) {
/* Add flexible `1fr` columns when larger than `100px`. */
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
}
section:last-child {
/* Same, but always fit them to the row! */
grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
}
body {
font-family: sans-serif;
padding: 20px;
}
section {
background-color: gold;
}
section:not(:first-child) {
margin-top: 20px;
}
div {
background-color: tomato;
border-bottom: 2px solid firebrick;
border-right: 2px solid firebrick;
padding: 5px;
}
<section>
<div>
<p>Item 1</p>
</div>
<div>
<p>Item 2 <br>with line <br>breaks</p>
</div>
<div>
<p>Item 3 <br>also</p>
</div>
<div>
<p>Item 4</p>
</div>
<div>
<p>Item 5</p>
</div>
</section>
<section>
<div>
<p>Item 1</p>
</div>
<div>
<p>Item 2 <br>with line <br>breaks</p>
</div>
<div>
<p>Item 3 <br>also</p>
</div>
<div>
<p>Item 4</p>
</div>
<div>
<p>Item 5</p>
</div>
</section>
<section>
<div>
<p>Item 1</p>
</div>
<div>
<p>Item 2 <br>with line <br>breaks</p>
</div>
<div>
<p>Item 3 <br>also</p>
</div>
<div>
<p>Item 4</p>
</div>
<div>
<p>Item 5</p>
</div>
</section>
Drag the divider over to see the difference in auto-fill
/ auto-fit
behaviors
grid-template-areas
Grid is really useful for scaffolding out layouts, and sometimes it is helpful to give your grid areas qualitative/descriptive names that reflect their usage. This also allows you to reference your grid items (children).
You can write it in a way that reflects the layout! Repeating the name of a grid area makes the content span those cells. The syntax itself then provides an ergonomic visualization of the grid structure (for us humans):
section {
display: grid;
grid-template-columns: 2fr 1fr;
grid-template-areas:
"header header "
"main sidebar"
"footer sidebar";
}
You can also name grid lines with [linename] length
syntax, but I’ve never found this very useful.
Item (child) properties
You can really start to see the power of grid when you use these properties on the individual grid items (children) within the containers. While the container (parent) properties usually make for uniform layouts, item (child) properties allow for unique structures.
grid-area
If you’ve defined grid-template-areas
(as above), you can then assign individual children to these areas:
section {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-template-rows: 100px 50vh 100px;
grid-template-areas:
"header header header"
"main main aside"
"footer footer aside";
}
header {
background-color: crimson;
grid-area: header;
}
main {
background-color: hotpink;
grid-area: main;
}
aside {
background-color: lightsalmon;
grid-area: aside;
}
footer {
grid-area: footer;
}
body {
font-family: sans-serif;
padding: 20px;
}
section {
background-color: tomato;
}
section > * {
border-bottom: 2px solid firebrick;
border-right: 2px solid firebrick;
padding: 5px;
}
<section>
<header>
<p>Item 1 will span across the top</p>
</header>
<main>
<p>Item 2 with a lot of text in it that takes up some space, again I am writing this manually for some reason, but we’re already here so I may as well just keep writing nonsense to increase the height and show the layout better</p>
</main>
<aside>
<p>Item 3 continues down the side</p>
</aside>
<footer>
<p>Item 4 might also have enough text to wrap and add height</p>
</footer>
</section>
This is the kind of common layout that was unnecessarily hard before grid!
grid-column
/ grid-row
You can also control item placement in unnamed (and implicit) grid areas with the grid-column
and grid-row
properties. These take two values, divided with a /
(because CSS is inconsistent), which specify the start line and end line. (These are technically shorthand properties, but I’ll allow it—they are easier to read!) There is also a span
value for bridging across tracks (it has nothing to do with the span HTML element btw.):
section {
display: grid;
grid-template-columns: 2fr 1fr;
}
div:first-child {
background-color: crimson;
grid-column: 1 / span 2;
}
div:nth-child(2) {
background-color: hotpink;
}
div:nth-child(3) {
background-color: lightsalmon;
grid-column: 2;
grid-row: 2 / 4; /* Instead of span. */
}
body {
font-family: sans-serif;
padding: 20px;
}
section {
background-color: gold;
}
div {
background-color: tomato;
border-bottom: 2px solid firebrick;
border-right: 2px solid firebrick;
padding: 5px;
}
<section>
<div>
<p>Item 1 will span across the top</p>
</div>
<div>
<p>Item 2 with a lot of text in it that takes up some space, again I am writing this manually for some reason, but we’re already here so I may as well just keep writing nonsense to increase the height and show the layout better</p>
</div>
<div>
<p>Item 3 continues down the side</p>
</div>
<div>
<p>Item 4 might also have enough text to wrap and add height</p>
</div>
</section>
Notice that we can leave off the end line if it doesn’t span
multiple tracks, and also that you either add a span
or a specific end line number.
You can also leave off the start line if you just want to specify a span
, regardless of where the item falls in the grid:
section {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-auto-flow: dense;
}
div:nth-child(3),
div:nth-child(6) {
grid-column: span 2;
grid-row: span 2;
}
body {
font-family: sans-serif;
padding: 20px;
}
section {
background-color: gold;
}
div {
background-color: tomato;
border-bottom: 2px solid firebrick;
border-right: 2px solid firebrick;
padding: 5px;
}
<section>
<div>
<p>Item 1</p>
</div>
<div>
<p>Item 2 <br>with line <br>breaks</p>
</div>
<div>
<p>Item 3 <br>also</p>
</div>
<div>
<p>Item 4</p>
</div>
<div>
<p>Item 5</p>
</div>
<div>
<p>Item 6</p>
</div>
<div>
<p>Item 7 with more text</p>
</div>
<div>
<p>Item 8</p>
</div>
<div>
<p>Item 9 longer</p>
</div>
<div>
<p>Item 10</p>
</div>
<div>
<p>Item 11</p>
</div>
</section>
I’ve added grid-auto-flow: dense;
to the container—allowing the fifth item to scoot up “before” the bigger one.
And if you specify non-contiguous rows or columns, grid will create as many implicit tracks as it needs to accommodate them—even if they are empty:
section {
display: grid;
/* Give the implicit tracks a size. */
grid-auto-rows: 80px;
grid-auto-columns: 1fr;
}
div:nth-child(2) {
grid-column: 2;
grid-row: 4;
}
div:nth-child(3) {
grid-column: 5;
grid-row: 2;
}
div:last-child {
grid-column: 6;
grid-row: 6;
}
body {
font-family: sans-serif;
padding: 20px;
}
section {
background-color: gold;
}
div {
background-color: tomato;
border-bottom: 2px solid firebrick;
border-right: 2px solid firebrick;
padding: 5px;
}
<section>
<div>
<p>Item 1</p>
</div>
<div>
<p>Item 2 <br>with line <br>breaks</p>
</div>
<div>
<p>Item 3 <br>also</p>
</div>
<div>
<p>Item 4</p>
</div>
</section>
Keep in mind that like order
in flex, this arrangement is only visual! Keep your DOM in a logical, semantic sequence.
Keep in mind that with both grid-area
and grid-column
/ grid-row
, you are able to tell multiple grid items to land in the same cell—there isn’t any kind of fancy collision-proofing. If this is what you want, you can use z-index
to specify which one is visually in front!
justify-self
/ align-self
Finally, just like flex—you can position individual grid items within their tracks using justify-self
and align-self
. The syntax is the same as align in flex, again—but as with justify-items
/ align-items
above, you don’t have to flip axes:
section {
display: grid;
grid-auto-rows: 100px;
grid-template-columns: repeat(4, 1fr);
}
div:first-child {
/* Defaults. */
justify-self: stretch;
align-self: stretch;
}
div:nth-child(2) {
justify-self: start;
align-self: start;
}
div:nth-child(3) {
justify-self: center;
align-self: center;
}
div:nth-child(4) {
justify-self: end;
align-self: end;
}
body {
font-family: sans-serif;
padding: 20px;
}
section {
background-color: gold;
}
div {
background-color: tomato;
border-bottom: 2px solid firebrick;
border-right: 2px solid firebrick;
padding: 5px;
}
<section>
<div>
<p>Item 1</p>
</div>
<div>
<p>Item 2 <br>with line <br>breaks</p>
</div>
<div>
<p>Item 3 <br>also</p>
</div>
<div>
<p>Item 4</p>
</div>
</section>
You can mix and match these justify/align values, of course.
This page was adapted (ever so slightly) from Michael Fehrenbach's page on CSS Grid.