From CSS in Depth by Keith J. Grant

This article delves deep into grid layout in CSS, how it works, and how you can make it work for you. Grid layout is also contrasted and compared to Flexbox.


Save 37% on CSS in Depth. Just enter code learncss into the discount code box at checkout at manning.com.


The emergence of grid layout was not like that of other CSS features such as flexbox. As browsers implemented early versions of flexbox, they made it available through the use of vendor prefixes. The original intention of vendor prefixes was to allow developers to experiment with the technology before using it in production. This is not how things played out, however.

It took several years to develop the flexbox specification to a stable point. In the meantime, developers got excited about the new feature and started using it, prefixes and all. Then, as the specification evolved, browsers updated their implementations. Developers had to update their code to match, but they also had to leave the old code in place to support older browsers as well. It made for a rough introduction of the feature into the world.

To prevent this from happening again, browser vendors approached grid layout differently. Instead of implementing it with vendor prefixes, they have implemented it as a feature the user must explicitly opt into. Developers could experiment with it to learn how it works, and report bugs. But as far as your average user is concerned, the browser support was effectively zero. While at the same time, the browsers had almost completely implemented it.

Instead of a long, drawn-out rollout of development and debugging, all major browsers were able to turn on a full-featured, mostly-debugged implementation of grid, virtually overnight. In March of 2017, they started flipping the switch. In the span of three weeks, Firefox, Chrome, Opera, and Safari all released updates to their browsers, enabling grid layout. Microsoft Edge followed suit in June 2017. In the span of three months, browser support grew from 0% to almost 70% of users. This is unprecedented in the world of CSS.

 Level 1 of the specification is stable, and all modern browsers now conform to it. This means grid layout is be ready for production use now, as long as you do a little work to ensure a reasonable fallback layout.

 

Enabling experimental features

 

Building a basic grid

Now, let’s create a simple grid layout to make sure it’s working in your browser. We’ll lay out six simple boxes in three columns as shown in figure 1.

Create a new page and link it to a new stylesheet. The markup for this grid is shown in listing 1. I’ve added the letters A through F so it is apparent where each element ends up in the grid. Add this to your page.


Figure 1 A simple grid with three columns and two rows


Listing 1 A grid with six items

  
 <div class="grid">         
   <div class="a">a</div>   
   <div class="b">b</div>   
   <div class="c">c</div>   
   <div class="d">d</div>   
   <div class="e">e</div>   
   <div class="f">f</div>   
 </div>
  

The grid container

The container’s children become the grid items

As with flexbox, grid layout applies to two levels of the DOM hierarchy. An element with display: grid will become a grid container. Its child elements then become grid items. We’ll then apply a few new properties to define the specifics of the grid. Open a stylesheet and add the styles from listing 2 to your it.

Listing 2 Laying out a basic grid

  
 .grid {
   display: grid;                        
   grid-template-columns: 1fr 1fr 1fr;   
   grid-template-rows: 1fr 1fr;          
   grid-gap: 0.5em;                      
 }
  
 .grid > * {
   background-color: darkgray;
   color: white;
   padding: 2em;
   border-radius: 0.5em;
 }
  

Make the element a grid container

Define the three columns of equal width

Define two rows of equal height

Apply a gutter between each grid cell

If your browser supports grid layout, this will render six equal-sized boxes in three columns. There are a number of new things going on here. Let’s take a closer look at them.

First, we’ve applied display: grid defining a grid container. The container will behave similar to a block display element, filling 100% of available width. The value inline-grid is also available, in which case the element will flow inline and will only be as wide as is necessary to contain its children. You will most likely not need inline-grid as often.

Next come the properties grid-template-columns and grid-template-rows. These define the sizes of each of the columns and rows in the grid. These use a new unit, fr, which represents each column’s (or row’s) fraction unit. This behaves essentially the same as the flex-grow factor in flexbox. The declaration grid-template-columns: 1fr 1fr 1fr declares three columns with an equal growth factor.

You do not necessarily have to use fraction units for each column or row. You can also use other measures such as px, em, or percent. Or you could mix and match. For instance, grid-template-columns: 300px 1fr would define a fixed-size column of 300px followed by a second column that will grow to fill the rest of the available space. A 2fr column would be twice as wide as a 1fr column.

Finally, the grid-gap property defines the amount of space to add to the gutter between each grid cell. You can optionally provide two values to specify vertical and horizontal spacing individually (e.g. grid-gap: 0.5em 1em).

I encourage you to experiment with these values to see how they affect the final layout. Add new columns or change their widths. Add or remove grid items. Continue to experiment with the other layouts throughout this article. This will be the best way to get the hang of things.

Anatomy of a grid

It is important to understand the various parts of a grid. I have already mentioned grid containers and grid items, which are the elements that make up the grid. There are four other important terms to know. These are illustrated in figure 3.


Figure 2 The parts of a grid


  • Grid line — These make up the structure of the grid. Grid lines can be vertical or horizontal, and lie on either side of a row or column. The grid-gap, if defined, lies atop the grid lines.

  • Grid track — A grid track is the space between two adjacent grid lines. A grid has horizontal tracks (rows) and vertical tracks (columns).

  • Grid cell — A single space on the grid, where a horizontal grid track and a vertical grid track overlap.

  • Grid area — A rectangular area on the grid made up by one or more grid cells. The area is between two vertical grid lines and two horizontal grid lines.

You will refer to these parts of the grid as you build grid layouts. For instance, declaring grid-template-columns: 1fr 1fr 1fr defines three vertical grid tracks of equal width. It also defines four vertical grid lines: one down the left edge of the grid, two more between each grid track, and one more along the right edge.

Imagine for a moment that we have built a page using flexbox. Let’s take a look at that design and consider how we could implement it using grid. This is shown in figure 4. I have added dashed lines to indicate the location of each grid cell. Notice that some of the sections span multiple cells — filling a larger grid area.


Figure 3 Page layout with grid; dashed lines added to indicate location of each grid cell


This grid has two columns and four rows. The top two horizontal grid tracks are each dedicated to the page title (“Ink”) and the main menu. The main area fills the remaining two cells in the first vertical track, and the two sidebar tiles are each placed in one of the remaining cells in the second vertical track.

Note Your design does not need to fill every cell of the grid. Leave a cell empty where you wish to add white space.

It’s important to note, the use of grid here does not render flexbox useless. As we go through the page, you will see that flexbox is still an important part of the layout. I will point out places we will still use it in on the page where it makes sense.

Rebuilding this page with grid layout will require a different HTML structure. When we built this page using flexbox, we had to nest the elements a certain way. We used one flexbox to define columns and nested another flexbox inside it to define rows. To build this with grid requires that we flatten out the HTML: each item we place on the grid must be a child of the main grid container. This new markup is shown in listing 3. Create a new page to match.

Listing 3 HTML structure for grid layout

  
 <body>
   <div class="container">               
     <header>                            
       <h1 class="page-heading">Ink</h1>
     </header>
  
     <nav>                               
       <ul class="site-nav">
         <li><a href="/">Home</a></li>
         <li><a href="/features">Features</a></li>
         <li><a href="/pricing">Pricing</a></li>
         <li><a href="/support">Support</a></li>
         <li class="nav-right">
           <a href="/about">About</a>
         </li>
       </ul>
     </nav>
  
     <main class="main tile">            
       <h1>Team collaboration done right</h1>
       <p>Thousands of teams from all over the
         world turn to <b>Ink</b> to communicate
         and get things done.</p>
     </main>
  
     <div class="sidebar-top tile">      
       <form class="login-form">
         <h3>Login</h3>
         <p>
           <label for="username">Username</label>
           <input id="username" type="text"
             name="username"/>
         </p>
         <p>
           <label for="password">Password</label>
           <input id="password" type="password"
             name="password"/>
         </p>
         <button type="submit">Login</button>
       </form>
     </div>
  
     <div class="sidebar-bottom tile centered">     
       <small>Starting at</small>
       <div class="cost">
         <span class="cost-currency">$</span>
         <span class="cost-dollars">20</span>
         <span class="cost-cents">.00</span>
       </div>
       <a class="cta-button" href="/pricing">
         Sign up
       </a>
     </div>
   </div>
 </body>
  

The “container” becomes our grid container

Each grid item must be a child element of the grid container

This version of the page has placed each section of the page as a grid item: the header, the nav, the main, and the two sidebars. I’ve also added the “tile” class to the main and the two sidebars, as this class provides the white background color and the padding that these elements have in common.

Let’s apply grid layout to the page, and put each section in place. We’ll pull in a lot of styles momentarily, but first let’s get general shape of the page in place; I find it is generally easier to build a page from the outside in. After building the basic grid, the page will appear as in figure 5.


Figure 4 Page with basic grid structure in place


Create an empty stylesheet and link to it from the page. Add listing 4 to the new stylesheet. This code introduces a few new concepts, which I will walk you through.

Listing 4 Apply top level page layout using grid

  
 :root {
   box-sizing: border-box;
 }
  
 *,
 ::before,
 ::after {
   box-sizing: inherit;
 }
  
 body {
   background-color: #709b90;
   font-family: Helvetica, Arial, sans-serif;
 }
  
 .container {
   display: grid;
   grid-template-columns: 2fr 1fr;       
   grid-template-rows: repeat(4, auto);  
   grid-gap: 1.5em;
   max-width: 1080px;  
   margin: 0 auto;
 }
  
 header,
 nav {
   grid-column: 1 / 3;                   
   grid-row: span 1;                     
 }
  
 .main {
   grid-column: 1 / 2;                   
   grid-row: 3 / 5;                      
 }
  
 .sidebar-top {
   grid-column: 2 / 3;                   
   grid-row: 3 / 4;                      
 }
  
 .sidebar-bottom {
   grid-column: 2 / 3;                   
   grid-row: 4 / 5;                      
 }
  
 .tile {
   padding: 1.5em;
   background-color: #fff;
 }
  
 .tile > :first-child {
   margin-top: 0;
 }
  
 .tile * + * {
   margin-top: 1.5em;
 }
  

 Define two vertical grid tracks

Define four horizontal grid tracks of size “auto”

Span from vertical grid line “1” to grid line “3”

Span exactly one horizontal grid track

Position other grid items between various grid lines

There are a number of new concepts in this listing. Let’s take them one at a time.

We set the grid container and defined its grid tracks using grid-template-columns and grid-template-rows. The columns are defined using fraction units, “2fr” and “1fr”, so the first column will grow twice as much as the second. The rows use something new, the repeat() function. This function provides a shorthand for declaring multiple grid tracks.

This declaration, grid-template-rows: repeat(4, auto), defines four horizontal grid tracks of height “auto.” It is equivalent to grid-template-rows: auto auto auto auto. The track size of “auto” will grow as necessary to the size of its contents.

You may also define a repeating pattern with the repeat() notation. For instance, repeat(3, 2fr 1fr) defines six grid tracks, by repeating the pattern three times (resulting in: 2fr 1fr 2fr 1fr 2fr 1fr). See figure 6 for an illustration of the resulting columns.

Or you can use repeat() as part of a longer pattern. grid-template-columns: 1fr repeat(3, 3fr) 1fr, for instance, defines a 1fr column followed by three 3fr columns then another 1fr (or 1fr 3fr 3fr 3fr 1fr). As you can see, this is a bit tricky to parse visually, which is why the repeat() shorthand comes in handy.


Figure 5 Use the repeat() function to define a repeating pattern in a template definition


Numbered grid lines

With the grid tracks defined, we then have some styles placing each grid item into a specific location on the grid. These are positioned using numbers that represent the grid lines. The grid lines are assigned numbers, as shown in figure 7.


Figure 6 Grid lines are numbered beginning with 1 on the left/top. Negative numbers may be used to refer to position from the right/bottom


These numbers can be used to indicate where to place each grid item using the grid-column and grid-row properties. For instance, if you want a grid item to span from grid line one to grid line three, apply grid-column: 1 / 3 to the element. Or apply grid-row: 3 / 5 to a grid item make it span from horizontal grid line three to grid line five. These two together specify the grid area we want.

In our page, several grid items are positioned this way:

  
 .main {
   grid-column: 1 / 2;
   grid-row: 3 / 5;
 }
  
 .sidebar-top {
   grid-column: 2 / 3;
   grid-row: 3 / 4;
 }
  
 .sidebar-bottom {
   grid-column: 2 / 3;
   grid-row: 4 / 5;
 }
  

This positions the main in the first column (between grid lines 1 and 2) and spanning the third and fourth row (between grid lines 3 and 5). It places each sidebar tile in the right column (between grid lines 2 and 3), stacked atop each other in the third and fourth rows.

Note These are, in fact, shorthand properties. grid-column is shorthand for grid-column-start and grid-column-end; grid-row is shorthand for grid-row-start and grid-row-end. The slash is only needed in the shorthand version to separate the two values.

The ruleset that positions the header and nav at the top of the page is a little bit different. Here I’ve used the same ruleset to target both the header and the nav:

  
 header,
 nav {
   grid-column: 1 / 3;
   grid-row: span 1;
 }
  

This uses grid-column as we’ve seen, making them span the full width of the grid. But grid-row and grid-column have a special keyword, span. This tells the browser that the item will span one grid track. We didn’t specify an explicit row to start or end at, so it will be placed automatically using the grid item placement algorithm. They will fill the first available space on the grid where they fit: the first and second rows. We’ll look a little closer at auto-placement later in the chapter.

Working together with flexbox

After learning about grid, developers often ask about flexbox. Are these two competing layout methods? The answer is no; they are complimentary. They were largely developed in conjunction. There is some overlap in what each can accomplish, but they each shine in different scenarios. There are two important distinctions between the two, and choosing between flexbox and grid for a piece of a design is going to come down to your particular needs.

The first distinction is that flexbox is primarily one-dimensional. It is ideal for rows (or columns) of similar elements. It does support line wrapping using flex-wrap, but there is no way to align items in one row with those in the next. Grid, on the contrary, is two-dimensional. It is intended to be used in situations where you want to align items in one track with those in another. This distinction is illustrated in figure 8.


Figure 7 Flexbox aligns items in one direction. Grid aligns items in two directions.


The second major distinction, as articulated by CSS WG member Rachel Andrew, is this: flexbox works from the content out, while grid works from the layout in. Flexbox allows you to arrange a series of items in a row or column, but their sizes don’t need to be explicitly set. Instead, the contents will make most of the decisions about how much space each item needs.

With grid, you are first and foremost describing a layout, then placing items into that structure. There is some ability for the contents of each grid item to influence the size of its grid track, but that will affect the size of the entire track, and thus the size of other grid items in the track.

In our page, we’ve positioned the main regions of the page using grid, because we want the contents to adhere to this layout. But for other items on the page, such as the nav menu, we can allow the contents to have a greater influence on the outcome—items with more text can be wider; items with less text can be narrower. It is also a horizontal (one-dimensional) layout. For both of these reasons, flexbox is an appropriate solution.

Let’s style these items to finish the page (figure 9). This includes the top navigation, which consists of a list of links aligned horizontally. We will also use flexbox for the stylized pricing number on the lower right. After adding these and a few other styles, we will arrive at the page’s final look and feel.


Figure 8 Fully styled page


These styles are identical to those from our hypothetical Flexbox page, minus the high-level layout we have now applied using grid. I have repeated them in listing 5. Go ahead and add these to your stylesheet.

Listing 5 Remaining styling for the page

  
 .page-heading {
   margin: 0;
 }
  
 .site-nav {
   display: flex;   
   margin: 0;
   padding: .5em;
   background-color: #5f4b44;
   list-style-type: none;
   border-radius: .2em;
 }
  
 .site-nav > li {
   margin-top: 0;
 }
  
 .site-nav > li > a {
   display: block;
   padding: .5em 1em;
   background-color: #cc6b5a;
   color: white;
   text-decoration: none;
 }
  
 .site-nav > li + li {
   margin-left: 1.5em;
 }
  
 .site-nav > .nav-right {
   margin-left: auto;
 }
  
 .login-form h3 {
   margin: 0;
   font-size: .9em;
   font-weight: bold;
   text-align: right;
   text-transform: uppercase;
 }
  
 .login-form input:not([type=checkbox]):not([type=radio]) {
   display: block;
   margin-top: 0;
   width: 100%;
 }
  
 .login-form button {
   margin-top: 1em;
   border: 1px solid #cc6b5a;
   background-color: white;
   padding: .5em 1em;
   cursor: pointer;
 }
  
 .centered {
   text-align: center;
 }
  
 .cost {
   display: flex;     
   justify-content: center;
   align-items: center;
   line-height: .7;
 }
  
 .cost > span {
   margin-top: 0;
 }
  
 .cost-currency {
   font-size: 2rem;
 }
 .cost-dollars {
   font-size: 4rem;
 }
 .cost-cents {
   font-size: 1.5rem;
   align-self: flex-start;
 }
  
 .cta-button {
   display: block;
   background-color: #cc6b5a;
   color: white;
   padding: .5em 1em;
   text-decoration: none;
 }
  

 Flexbox menu

Stylized “cost” using flexbox

In conclusion, when your design calls for alignment of items in two dimensions, use grid. And when you are only concerned with one direction, use flexbox. In practice, this will often (but not always) mean grid makes the most sense for high-level layout of the page, and flexbox makes sense for certain elements within each grid area. As you continue to work with both, you will begin to get a feel for which is appropriate in various instances.


If you’re interested in really learning CSS, check out CSS in Depth on liveBook and see this Slideshare presentation.