A List Apart

Menu

Practical CSS Grid: Adding Grid to an Existing Design

Understanding and using CSS Grid is easier than you might expect. The day Grid support shipped in Firefox 52, I decided on the spur of the moment to convert the basic layout of my personal site to use Grid. And it was a fairly simple process—five minutes to write the grid styles, then 15-20 spent troubleshooting.

Article Continues Below

Grid allows us to literally define column and row grid lines, and then attach elements to those lines in any order we choose. That may sound like tables, but Grid is so much more than tables ever dreamed.  It means more responsive layouts, far more accessible documents, and far cleaner markup than even floats and positioning ever afforded us.

It’s been decades since CSS first emerged, but it’s never contained a system anything like this. And Grid is already supported in both Chrome and Firefox, with Safari coming soon (its Technology Preview releases support Grid as of this writing). A new era in digital design is dawning right now.

The way things used to be

Before we get to the Grid, allow me to take just a moment to explain the markup structure of meyerweb’s main pages, and the positioning-and-margins approach I’ve been using for, um, about 12 years now. Here’s how the markup is structured:


<body>
   <div id="sitemast">…</div>
   <div id="search">…</div>
   <div id="main">…</div>
   <div id="extra">…</div>
   <div id="navigate">…</div>
   <div id="footer">…</div>
</body>


Some of those IDs are idiosyncratic holdovers from my early-2000s view of layout and naming conventions. #extra, for example, is what most of us would call #sidebar. #sitemast stands in for #masthead. And #footer is from the days before the actual <footer> element

The divs (which should probably be sections these days, but never mind that now) are arranged the way they are so that if the CSS fails to load, or a speaking browser is used to browse the site, then the site’s masthead is first, the ability to search the site is second, and the main content of the page is third. After that, extra materials, site navigation, and the footer follow.

All of these were stitched together into a layout by absolutely positioning the navigate and search divs. The sitemast was set to be 192px tall, and both the navigate and search divs were given top: 192px; to show up just below it. In order to leave space for them to land, top margins were applied to the main and extra divs. (Fig. 1)

Screenshot of web page
Fig. 1: meyerweb’s home page (foreshortened)
 

Constructing the grid

So that’s how things have been laid out since the middle of 2005, more or less. I fiddled with a flexbox layout at one point as an experiment, but never shipped it, because it felt clumsy to be using a one-dimensional layout tool to manage a two-dimensional layout. I probably should have converted the navigation bar to flexbox, but I got distracted by something else and never returned to the effort.

Besides, Grid was coming. In the run-up to Grid support being released to the public, I was focused on learning and teaching Grid, creating test cases, and using it to build figures for publication. And then, March 7th, 2017, it shipped to the public in Firefox 52. I tweeted and posted an article and demo I’d put together the night before, and sat back in wonderment that the day had finally come to pass. After 20+ years of CSS, finally, a real layout system, a set of properties and values designed from the outset for that purpose.

And then I decided, more or less in that moment, to convert my personal site to use Grid for its main-level layout. It took me less than five minutes to come up with the following:


body {
   display: grid;
   grid-template-rows: 192px min-content min-content 1fr;
   grid-template-columns: 1fr 20em;
}

#sitemast {
   grid-row: 1; 
   grid-column: 1 / span 2;
}

#search {
   grid-row: 2; 
   grid-column: 2;
}

#main {
   grid-row: 3; 
   grid-column: 1;
}

#extra  {
   grid-row: 3; 
   grid-column: 2;
}

#navigate {
   grid-row: 2; 
   grid-column: 1;
}

#footer {
   grid-row: 4; 
   grid-column: 1;
}

That’s not all I had to do, but it’s the core. Let me break it down for you.


body {
   display: grid;
   grid-template-rows: 192px min-content min-content 1fr;
   grid-template-columns: 1fr 20em;
}

This part of the CSS sets the body element to be a grid container and sets up the grid lines. When you make an element a grid container, all of its children become grid items. (If you’ve worked with flexbox, then this pattern will be familiar to you.) So with that display: grid, I turned all of the child divs into grid items.

Next come the rows in the grid. The values in grid-template-rows actually define separation distances between grid lines (the same is true of grid-template-columns, which we’ll get to in a moment). So the value 192px min-content min-content 1fr; means: “Go 192 pixels down from the top of the grid container and drop a grid line. Then drop another two such that they provide enough vertical space for the contents of the rows they define. Finally, leave one fr (fraction) of distance between the third grid line and the bottom of the grid container.” (Fig. 2)

Screenshot of web page
Fig. 2: Defining the rows
 

The value min-content is pretty cool. It means just what it says: “Take up the minimum amount of space needed to fit the contents.” So for the second row, the one that will contain the navigation bar and search field, it will be as tall as the taller of the two, and no taller.

Ditto for the third row, the one containing the main and extra divs. On the homepage, the main div will be taller. On subpages, that might not always be the case. In all circumstances, the row containing those two divs will always be tall enough to contain them both.

With the rows figured out, next come the columns. I decided to keep things simple and just set up two. If you look at meyerweb’s home page, it appears to have three columns, but that’s only true of blog posts—a substantial but minority part of the site—and the left-side “column” is more of a sidebar inside the main column.

In the original design, the sidebar (#extra) is 18em wide, with some extra space to keep it away from the main column. But the column also has to fit the search box, which is a bit wider. After some experimentation, I settled on a width of 20em. The rest was left to flex as 1fr. (Fig. 3)

Screenshot of web page
Fig. 3: Defining the columns
 

Now that I’ve used the fr unit twice, a few words of explanation are in order. fr stands for “fraction,” and means “a fraction of the available unconstrained space.” In this grid, there are two columns. One of them has an explicit width of 20em, which is thus constrained—there’s no room for flexibility. The rest of the column space is unconstrained—as the width of the grid container changes (say, due to changes of the browser window) the unconstrained space will change to be the container’s width minus the 20em of constrained space.

Imagine for a moment I’d decided to split the grid into four columns, with the rightmost being 20em wide and the rest being equal, flexible widths. That would have looked like:

grid-template-columns: 1fr 1fr 1fr 20em;

Alternatively, I could have written it as:

grid-template-columns: repeat(3, 1fr) 20em;

In any event, that would have caused the unconstrained space to be divided equally among the first three columns. If the grid container were 65em wide, the last column would be 20em wide, and the other three 15em each. (3 x 15 = 45; 45 + 20 = 65.) Shrink the grid container down 50em wide, and the first three columns would shrink to 10em each.

In my case, I wanted that first column to take all of the space not given to the constrained last column, so it got 1fr. The final result is shown in Fig. 4.

Screenshot of web page
Fig. 4: The complete grid
 

Placing the items

With the grid lines set up, now it’s just a matter of attaching grid items to the grid lines. This can be done automatically, using the grid-flow algorithm, but this is a case where I want to place each item in a specific place. That leads to the following:


#sitemast {
   grid-row: 1; 
   grid-column: 1 / span 2;
}

#search {
   grid-row: 2; 
   grid-column: 2;
}

#main {
   grid-row: 3; 
   grid-column: 1;
}

#extra {
   grid-row: 3; 
   grid-column: 2;
}

#navigate {
   grid-row: 2; 
   grid-column: 1;
}

#footer {
   grid-row: 4; 
   grid-column: 1;
}

For each of the six divs, I simply said, “Pin your top edge to this row line, and your left edge to this column line.” I used line numbers because that’s all I gave myself—it’s possible to assign names to grid lines, but I didn’t. (But stay tuned for an example of this, later in the article!)

So, to pick one example, I set up the #main portion to start on the third row line and the first column line. That means it will, by default, fill out the space from the first to second column lines, and from the third to fourth row lines.

Almost all of the divs were set up in this way. The exception in this case is the #sitemast. It starts at the first column and row lines, but since I wanted it to go all the way across the grid, I set its column value to 1 / span 2. That means “Start at column line 1, and span across two columns.” I could have gotten the same result with the value 1 / 3, which means “Go from column line 1 to column line 3.” (Fig. 5)

Screenshot of web page
Fig. 5: The grid items’ placement
 

But realize: that’s just a diagram, not the actual layout situation. Not yet, at any rate.
Something I want to be clear about here is that while you can explicitly assign all of your grid items to specific rows and columns, you don’t have to do so. Grid has a flow model that allows grid items to be automatically assigned to the next open grid cell, depending on the flow direction. In my case, I could have gotten away with literally just these rules:


#sitemast {
   grid-column: 1 / span 2;
}

#navigate {
   grid-row: 2; 
   grid-column: 1;
}

That would have ensured the masthead was two columns wide, and that the navigation div was placed in the exact grid cell I wanted. That would have left the second row’s first cell filled by navigation, and the rest of the grid cells open.

Given that, the unassigned items would be flowed into the grid in source order. The masthead (#sitemast) would be placed in the first two-column row it could find, which turns out to be the first row. The search div would flow into the next open cell, which is row 2, column 2, because row 2, column 1 is already occupied by the navigation div. After that, the main div would flow into the first open cell: row 3, column 1. Extra would go into the next cell: row 3, column 2. And then the footer would be placed into row 4, column 1.

The end result would be exactly what’s shown in Fig. 5. The difference would be that if I had a special page where another div was added, it could throw off the whole layout, depending on where it appeared in the HTML. By explicitly assigning my layout pieces to the places I want them, I prevent a stray element from upending everything.

Given the styles I wrote, if a child element of the body is added to a page, it will become a grid item. If I don’t give it an explicit place in the grid, it will end up flowed into the first available grid cell. Since the lower-right cell (row 4, column 2) is unoccupied, that’s where the extra element would be placed…assuming it isn’t set to span two columns. In that case, it would end up at the bottom of the grid, in an automatically-created fifth row.

Accommodating the past

It’s easy enough to set up a grid, but when you drop grid items into it, they bring all of their existing styles in with them. That might not be a big deal in some cases, but in mine, it meant all of the margins and padding I’d used to keep the layout pieces apart from each other were now messing with the placement of the grid items. You can see this in Fig. 6, created using a local copy of the site.

Screenshot of web page
Fig. 6: Grid + legacy = yoinks
 

Ouch. It was time to override the pieces of the legacy layout styles I didn’t need in Grid, but did need to keep for browsers that don’t yet understand Grid.

So I wrapped the whole bit in an @supports block. Since I wanted to constrain the grid layout to wider displays, I put an @media block just inside @supports, and then proceeded to zero out or otherwise change the various margins and padding I didn’t need in a Grid context. Here’s how it turned out:


@supports (display: grid) {
   @media (min-width: 60.001em) {
      body {
         display: grid;
         grid-template-rows: 192px min-content min-content 1fr;
         grid-template-columns: 1fr 20em;
      }

      #sitemast {
         grid-row: 1; 
         grid-column: 1 / span 2;
      }

      #search {
         grid-row: 2; 
         grid-column: 2;
         position: static; 
         padding: 0.25em 0 1em;
      }

      #main {
         grid-row: 3; 
         grid-column: 1;
         margin-right: 0; 
         margin-top: 1.25em;
         padding-top: 0;
      }
   
      .hpg #main {
         margin-top: 0; 
         padding-top: 0;
      }

      #extra {
         grid-row: 3; 
         grid-column: 2;
         position: static; 
         top: 0;
         margin-top: 0;
         padding-top: 0.5em; 
         margin-left: auto;
      }

      #navigate {
         grid-row: 2; 
         grid-column: 1;
         position: static; 
         margin-top: 1px; 
         padding-bottom: 0;
      }

      #footer {
         grid-row: 4; 
         grid-column: 1;
         margin-right: 0;
      }
   }
}

I probably could refactor that to be more efficient, but for now, I’m going to leave it as-is. It makes clear what had to be done to which grid item—which ones needed to override position so their absolute positioning didn’t interact weirdly with the grid, which margins and padding needed to be changed, and so on. Let’s look at the end result (Fig. 7).

Screenshot of web page
Fig. 7: Grid + @supports = yowza!
 

You might be forgiven for thinking that this was much ado about not very much. Why go to all that effort just to make it look the same? The real power here, in what is admittedly a simple case, is how I no longer have to worry about overlap. The footer will always be below the main and extra divs, no matter which is taller. When I was using positioning, that was never guaranteed.

Similarly, the navigation and search will always maintain a shared height, making sure neither will overlap with the content below them—and thanks to min-content, I don’t have to guess at how tall they might get. Grid just handles all that for me.

And remember, the layout still functions in old browsers just as it always did, using positioning. I didn’t “break” the site for browsers that don’t understand Grid. The more capable Grid layout is there, waiting for browsers like Chrome and Firefox that understand it.

If you want to see all this live for yourself, head over to meyerweb.com and inspect elements in Firefox 52 or later. There you’ll see a little waffle icon next to the display: grid declaration on the body element. Click it, and Firefox will draw the grid lines on the page for you to scrutinize. (You can also enable a more powerful layout tool in Nightly builds of Firefox; see my post “ Grid Inspection ” for details.)

Naming conventions

I mentioned earlier that it’s possible to name grid lines. I didn’t do it for my own styles because the grid I defined was so simple, but for more complicated grids, naming the lines might be useful.

Using the stripped-down version of the styles, the one without all the legacy overrides, naming the grid lines would look something like this:


body {
   display: grid;
   grid-template-rows: [masthead] 192px [navsearch] min-content [mainextra] min-content [footer] 1fr;
   grid-template-columns: [left] 1fr [middle] 20em [right];
}

Each of those square-bracketed words is assigned as a name to the corresponding grid line. (Fig. 8)

Screenshot of web page
Fig. 8: Named grid lines
 

Once those names are defined, you can refer to them in your grid-row and grid-column properties. For example:


#sitemast {
   grid-row: masthead; 
   grid-column: left / span right;
}

#search {
   grid-row: navsearch; 
   grid-column: middle;
}

#main {
   grid-row: mainextra; 
   grid-column: left;
}

#extra  {
   grid-row: mainextra; 
   grid-column: middle;
}

#navigate {
   grid-row: navsearch; 
   grid-column: left;
}

#footer {
   grid-row: footer; 
   grid-column: left;
}

Much like class names, you can assign multiple names to a grid line by supplying a space-separated list. Try this one for size:

grid-template-columns: [start left] 1fr [middle sidebar] 20em [right end];

You can then refer to any one of those names in your grid-column declaration. There’s no defined limit on the number of names, but remember what comes with great power.

In case you were wondering, you can mix grid line names and numbers, so something like grid-row: navsearch; grid-column: 2;} is completely fine. You can use any name the browser can parse, which means you can specify just about anything Unicode and your CSS file’s character encoding will allow.

Grid and Flexbox

A question you may have is: now that we have Grid, do I throw away Flexbox? Absolutely not! The two can and do work very well together.

Consider the navigation bar of my design. For years, it’s been laid out using an unordered list and float: left for the list items. Simplified a bit, the CSS and markup looks like this:


#navlinks {
  float: left; 
  width: 100%;
}

#navlinks li {
  float: left; 
  list-style: none; 
  margin-left: 1px;
}


<div id="navigate">
   <ul id="navlinks">
     <li><a href="…">Archives</a></li>
     <li><a href="…">CSS</a></li>
     <li><a href="…">Toolbox</a></li>
     <li><a href="…">Writing</a></li>
     <li>><a href="…">Speaking</a></li>
     <li>>><a href="…">Leftovers</a></li>
   </ul>
</div>

Why not display: inline-block instead of float: left? Because that literally wasn’t an option when I wrote the CSS for the navlinks, and I never got around to updating it. (You may be sensing a theme here.)

Now I have two much better options for arranging those links: Grid and Flexbox. I could define a grid there, which would go something like this:


#navlinks {
  display: grid;
  grid-template-columns: repeat(6,min-content);
}

#navlinks li {
  list-style: none; 
  margin-left: 1px;
}

That would essentially get the same result, only in a grid, which is far more robust than either floats or inline blocks.

On the other hand, I’d be using Grid, which is a two-dimensional layout system, for a one-dimensional piece of layout. It’s certainly possible to do this, but it feels a little like overkill, and it’s not really what Grid was designed to do. Flexbox, on the other hand, is designed for exactly these kinds of situations.

So I might write the following instead:


#navlinks {
  display: flex; 
  justify-content: flex-start; 
  flex-wrap: wrap;
}

#navlinks li {
  list-style: none; 
  margin-left: 1px;
}

Again, that would be basically the same result, but in a more robust fashion. In addition to keeping the links all lined up, the wrap value will let the links go to a second line if need be. And because the flexbox sits inside a grid item that’s part of a grid row whose height is min-content, any increase in height (due to line wrapping or whatever) will cause the entire row to become taller. That means the rows after it will move down to accommodate it.

And now that I look at the markup again, I’ve realized I can simplify that markup without needing to touch any grid styles. Instead of wrapping a list with a div, I can drop the div and reassign its ID to the list. So the markup can become:


<ul id="navigate">
  <li><a href="…">Archives</a></li>
  <li><a href="…">CSS</a></li>
  <li><a href="…">Toolbox</a></li>
  <li><a href="…">Writing</a></li>
  <li><a href="…">Speaking</a></li>
  <li><a href="…">Leftovers</a></li>
</ul>

After adjusting the selectors in my CSS from #navlinks to #navigate, the resulting layout will be exactly as it was before. The ul will become a grid item and a flex container. That is a thing you can do.

The downside in my case would be dealing with any interactions between that change and my legacy layout, but it’s not a huge issue to solve. It’s just a matter of doing it.

Letdowns

So what are the down sides?  Not many, but they do exist.

Most fundamentally, there’s no way to define an overall page grid that has all items relate to it. In other words, if I say:


body {
 display: grid;
 grid-template-columns: repeat(16, 1fr);
}

…then that sets up a 16-column flexible grid for the body element only, and its child elements are the only ones that can become grid items. I can’t reach down into the document tree and assign elements to be placed on that body grid. That’s the main reason I didn’t try to put the little sidebar bits on my blog posts into a shared grid: I literally can’t, at this point, unless I resort to ugly CSS or HTML hackery.

The capability to do such things is known as subgrid, and it hasn’t been implemented by any browsers as yet. There are questions as to exactly how it should or shouldn’t work, so there’s still plenty of hope that everything will work out in the end. It’s a disappointment that we don’t have it yet, and that lack restricts the full range of grid’s power, but hopefully only for a short while.

In the meantime, I’m sure people will come up with ways to work around this limitation. A basic workaround in this case: I could define a grid that applies to every blog post individually, and arrange the pieces of each post on those nested grids. The CSS would look something like:


div.post {
  display: grid;
  grid-template-columns: [meta] 10em [main] 1fr;
  grid-template-rows: [title] min-content [main] 1fr;
}

With that, I could place the metadata, the title, and the post’s body text into the defined grid cells, using either grid line numbers or the grid names I set up. Something like:


div.post h3 {
  grid-column: 2; 
  grid-row: title;
}

ul.meta {
  grid-column: meta; 
  grid-row: main;
}

div.post div.text {
  grid-column: main; 
  grid-row: main;
}

The drawback is that the metadata is then constrained to be a specific width, instead of my being able to set a column that all metadata shares, and size it by the longest bit of content.  That’s no worse than right now, where I’m setting the floated metadata to an explicit width, so this doesn’t lose me anything. It’s just a (temporarily) missed opportunity to gain something.

Another limitation, one that may or may not be addressed, is that you cannot directly style grid cells. Suppose I’d wanted to put a box around the #extra sidebar, completely filling out that cell. I’d have to style the div. I can’t do something like this:


@grid-cell(2, 3) {
  background: teal; 
  border: 1px solid;
}

I mean, I’m not even sure the syntax would look anything like that (probably not), and this sort of capability is only now starting to be debated by the Working Group. If you have use cases for this sort of capability, definitely share them with the world and the folks at www-style. The more real-world cases there are, the stronger the case for supporting them.

And there will, inevitably, be bugs to fix. For example, as I was finishing this article, I discovered that in some situations, Chrome 57 can suffer from a page-blanking bug when using Grid. It appears to be caused by having absolutely-positioned elements removed from a Grid page, and can be triggered by extensions like Window Resizer and LastPass. The good news is that a fix has been accepted for Chrome 58, so it should be fixed by the end of April 2017 at the latest.

Grid power

I hope this exploration of applying Grid to a live site has given you a taste of what’s possible. But I want to warn you that it’s just a taste, and a minor one at that. I was only able to scratch the surface of what the Grid syntax makes possible, so if this has captured your imagination, I strongly encourage you to experiment and then to dive into the Grid specification to see what else is possible. (Grid gaps! Dense grid packing! Inline grids! Auto-filling rows and columns!)

But even more, what I explored here was the barest wrinkle on the outer edges of a scratch on the surface of everything that Grid will make possible. Sure, it can make our existing designs more flexible, robust, and simple to maintain. That’s pretty great. It also makes possible layouts we’ve never even dreamed of, because they were impossible given the tools we had available. There are new techniques, even new art movements, waiting to be discovered. We haven’t experienced a phase shift this profound since the original move from tables to CSS. I hope you’ll be a part of exploring this new realm.

Resources

As I said, this is at best an introduction. Want to know more? Here are some great resources to get you going:

 

28 Reader Comments

Load Comments