← All writing
Craft · · 9 min

CSS Grid and the wrapper div

CSS HTML

I broke my first CSS Grid layout on Saturday, and it took me a genuinely embarrassing amount of time to figure out that the problem was an empty div.

Some context first. Firefox 52 came out last week with CSS Grid in it, Chrome 57 followed two days later, Safari has it in the preview builds, and Edge is still sitting on a much older draft. So it’s in two of the browsers I open every day, after years of being the thing people demoed at conferences while everyone in the room quietly thought about their support matrix. I have not been useful to anybody since.

Saturday I pulled up a client category page to see what it looked like rebuilt. Three lines of CSS, save, reload. I got one column. Full width, stacked all the way down the page like it’s 1998. I read that grid-template-columns line more times than I want to admit, checked I’d spelled minmax right, and eventually opened the new grid inspector in Firefox, which came in the same release and which cheerfully drew me an overlay with one enormous cell in it.

The cell was a wrapper div. I had typed it myself. My hands are ostensibly mine.

before.html
<!-- display: grid -->
<div class="products">
  <!-- the only grid item -->
  <div class="products__row">
    <article class="product">...</article>
    <article class="product">...</article>
    <article class="product">...</article>
  </div>
</div>
HTML

A grid container arranges its direct children and nothing further down than that. So the row wasn’t an inert box sitting there doing nothing, it was the grid, one item wide and one item tall, with the three products inside it being laid out by the row’s own block formatting, which stacks. The CSS was right the entire time. The page was showing me exactly what I had asked for.

The fix was deleting two lines.

products.css
.products {
  display: grid;
  grid-gap: 1.5rem;
  grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
}
CSS

Fit as many 14rem columns as will go, split up whatever’s left over, and put 1.5rem between them. No breakpoints, no percentages, no wrapper.

Row, row, row your div

My hands did that, I think, because in a float layout the wrapper isn’t decoration, it’s the mechanism. It’s doing (1) the clearing, so the parent has a height at all, (2) standing in as the thing every percentage width is a percentage of, and (3) holding the negative margin that cancels the gutter off the outside columns. Take the wrapper out of a float layout and the layout doesn’t get simpler, it falls on the floor.

I wrote a fairly tired post a while back about float: left being a layout system, and those wrappers are most of what it was complaining about without ever naming them. If you’ve used a 12-column framework you’ve typed the shape in your sleep too. A container, a row inside that, a .col-md-4 on every item, and you’re three elements deep before you reach anything a person would actually read. I’ve written before about counting the divs in one of my own headers and not enjoying the total, and a decent chunk of what I counted was this.

None of those three jobs survives contact with a grid. grid-gap puts the gutter between the tracks instead of inside the items, so there’s no outer margin to cancel and no :nth-child(3n) rule stripping one off the end. fr is a share of what’s left after the gutters have already come out, so there’s nothing for a percentage to be a percentage of. And nothing floats, so nothing needs clearing. The wrapper lost all three of its reasons to exist in the same week and my hands hadn’t gotten the memo.

The other half of the stylesheet

I went back through that template properly on Sunday and pulled out every element whose only job was arranging other elements. A dozen or so on the one page, plus a long run of class attributes that were, if you read them honestly, width instructions.

That’s the part I’m still turning over. I’d been writing the layout in two files and calling it CSS. Some of the arrangement lived in the stylesheet, where I could change it at a breakpoint, and the rest of it lived in the shape of the markup, where I couldn’t change it at all without editing the HTML, or duplicating it, or pushing and pulling with negative margins and hoping.

Grid takes that back. The container is a container, the items are the content, and where they end up is the stylesheet’s business: I can put the filters above the products at 40em and beside them at 60em and the document never finds out.

I don’t think that’s free though. A keyboard user tabs in DOM order and looks at the page in visual order. This is the first tool I’ve had that lets those two disagree quietly, at scale, with nothing on the screen to tell me I’ve done it. I don’t know where I’d draw that line yet. Somewhere well short of “reorder whatever you like,” and I suspect I’ll find the exact spot by getting it wrong on something real.

IE11 gets the simple one

None of this goes on a client site unguarded. IE11 has -ms-grid, which implements a draft from around 2011. It has no auto-placement, no grid-gap, no auto-fill. It wants every item positioned by hand. I took a look on Sunday night and I’m not maintaining two layouts.

So it goes in as the thing the support matrix I wrote about in January calls Grade B. IE11 gets the float layout, every product is on the page, every link works, and the products sit in three fixed columns instead of reflowing to fit. @supports does the branching, and the joke of it is that this works because IE11 doesn’t support @supports either, so the whole block is invisible to it.

progressive.css
.products__row::after {
  clear: both;
  content: "";
  display: table;
}

.product {
  float: left;
  margin-right: 2%;
  width: 32%;
}

.product:nth-child(3n) {
  margin-right: 0;
}

@supports (display: grid) {
  .products__row {
    display: grid;
    grid-gap: 1.5rem;
    grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
  }

  .product {
    float: none;
    margin-right: 0;
    width: auto;
  }
}
CSS

I’ve left the float layout’s own breakpoints out of that for space, but look at which element the grid is declared on. It’s the row. The float layout underneath is the thing that needs a wrapper to clear, so the wrapper stays and the grid goes on it. The entire pleasure of this feature is that it takes the layout out of my markup. And I don’t get to take the layout out of my markup this year, because the fallback for the browser I’m still supporting is the layout that put it there in the first place.

There is a property for precisely this. display: contents means “this box isn’t here for layout purposes, promote its children,” which would let the row exist for IE11 and evaporate for everyone else. It’s in Firefox. It is in nothing else. So, you know.

I said I’d believe it

A year and a half ago, in a post about flexbox becoming usable, I mentioned that a real two-dimensional grid spec was supposedly on the way, and that the only implementation of it anywhere was behind a flag in Internet Explorer, which struck me as a very funny place for the future to be living. I finished that thought like this:

I’ll believe it when I can use it.

Well. I can use it. I also can’t delete a single div. Both of those are true at once, and out of the two orders it could have arrived in, I’ll take the one where the good part turns up first and the cleanup is a year out.

This is the first layout tool anybody has handed me that doesn’t want anything from the HTML, and I keep coming back to that. Floats want a clearing parent. The framework wants a container, a row, and a class per breakpoint. Flexbox wants a row too, honestly, once you start building a page out of it instead of a component. Grid wants one container and then it leaves the document alone, and it took a broken page and an inspector drawing me a single enormous cell before I understood that was the difference.

One complaint before I stop, because I’ve been fairly breathless about this and there’s one thing I already know I’m going to want. A grid inside a grid item doesn’t line up with its parent’s tracks. It starts a fresh set of its own and there’s no way to tell it otherwise, because that got cut out of level 1 and pushed into a level 2 that doesn’t exist yet. It’s the omission I expect to miss most, because “the card’s internals line up with the page grid” is something designers ask for constantly. I’m going to go on faking it with a shared set of values and a lot of squinting.

If you’re going to try it this week, go and put display: grid on a container in something you already built. If it comes out as one tall column then congratulations, you’ve found your wrapper, and you’ll figure out what this thing is actually doing a lot faster than I did.

Read similar posts
6 min

content: "\00a0"

Why the most-used string in my stylesheets is an invisible one, how CSS escapes will silently eat the character after them, and the parts of the content property that aren't strings at all.

2 min

light-dark()

Two color values in one declaration, picked by the color scheme, which removes most of the reason a theme needed a second block of custom properties at all.