Flex items are not grid columns
Tags: css, flexbox, grid, webdev
Published: 2019-04-01 16:59:53

---
title: Flex items are not grid columns
published: true
description: There's a subtle but very important difference between how `flex-grow` and other growy-shrinky Flexbox rules behave and how the `fr` unit of CSS Grid works. Here we explore it using a practical example.
tags: css, flexbox, grid, webdev
series: CSS Layout is Awesome
---

We often talk about Flexbox's growy-shrinky rules, like `flex-grow` and `justify/align-content/items`, as working basically identically to the `fr` unit introduced with CSS Grid. That works as an introduction to `fr`s for devs who are familiar with Flexbox, but it's not really true. There's a subtle but very important difference that sometimes shows up.

# Scenario: Three differently-sized text items in a row

Let me lay down a scenario, as I love to do. This is a specific case of a general scenario that I have encountered pretty often, and it was a common frustration that's solved neatly by CSS Grid.

Consider a case where we have 3 inline elements, e.g. text labels, buttons, or images, of different sizes that need to be displayed neatly in a row. We want the first label to be left-aligned, the last one to be right-aligned, and the middle one to be centered on the page.

Here's an example of this that comes from an old weekend project of mine from college (that I hope to write more extensively about in future articles, but that's beside the point). This project was an experiment with the HTML5 `<canvas>` element, and it had an element of keyboard and mouse interaction. There were three primary controls, and I had three text labels above the canvas listing these controls:

![Three text labels](https://thepracticaldev.s3.amazonaws.com/i/dx56zll2as89z11cv4qp.png)

So that's the goal. The most important requirement, the one that will cause problems, is that the middle item must be centered on the page: its center line should match the center line of the page. (In some cases, "page" is just "container", but "page" is easier to demonstrate.) I've included the heading above the labels to give a useful visual landmark to compare against.

# Old school cool

Back in college, in the pre-flexbox days of 2012(ish), I solved this with some *very* brittle HTML+CSS that used a lot of absolute positioning:

{% codepen https://codepen.io/kenbellows/pen/VRqqGE default-tab=result %}

I've absolutely positioned the first and last labels to be on the same line as the middle element, stuck to the left or right. This... *works*, of course, but could be disrupted by certain changes to the surrounding layout, is very non-reactive, is pretty unreadable, and is inflexible: if you wanted to add a fourth control, everything would become a lot more complex. 


# Flexbox

If we were going to write this with modern CSS, the first thought might be to use Flexbox. It was certainly my first thought; this is a 1-d layout situation, which is what Flexbox is best at. Plus, I was pretty sure I could do it in two lines of CSS:

```css
#controls-body {
    display: flex;
    justify-content: space-between;
}
```

Done! Right?

{% codepen https://codepen.io/kenbellows/pen/JzwBBR default-tab=result %}

... Nope.

While we do have three items, with one pulled left, another pulled right, and the other floating between them, you should see right away that the middle item is not centered properly; namely, instead of being centered *on the page*, it's centered *between the other items*. This would work fine if the items on the end are always the same size, but otherwise it's a no-go.

Another thing you might try is to not use `justify-contents` on the parent, but instead give all columns `flex-grow: 1`, then add some `text-align` rules to each. But this turns out to have exactly the same visual result:

{% codepen https://codepen.io/kenbellows/pen/drwagy default-tab=result %}

Why don't these work? Well, we'll get to that in a minute. First, we'll look at something that does work.

# Grid

Let's take the intuition from the second Flexbox attempt, to give each inline item its own "column" and make those "columns" the same size, and translate it over to CSS Grid, where we can drop the quotes: we'll literally define a column in a grid for each item and give them the same size using the `fr` unit introduced with Grid.

There are two ways to do this. The most obvious is to define three explicit columns:

```css
#controls-body {
    grid-template-columns: 1fr 1fr 1fr;
}
```

Nice and clean. But I like to future-proof my code, as long as it doesn't add much work or complexity. So instead of explicitly defining a set of columns, let's use Grid's auto-flow rules to handle any number of items, in case I want to add more controls to my demo later on:

```css
#controls-body {
    grid-auto-flow: columns;  /* automatically place new items in new columns */
    grid-auto-columns: 1fr;   /* auto-columns should be 1fr wide */
}
```

Combine these rules with the `text-align` rules above, and we get a winner:

{% codepen https://codepen.io/kenbellows/pen/moaGKo default-tab=result %}


So a grid with three `1fr` columns works, but a flex container with three `flex-grow: 1` items, or with `justify-content: space-between`, doesn't. Why? What's the difference?


# The difference

So here's the thing. We often talk about Flexbox's flexy "distribute space evenly" rules, like `flex-grow` and `justify-content: space-between`, as being effectively the same as the `fr` unit introduced with CSS Grid. That works as an intuitive explainer of `fr`s for devs who are familiar with Flexbox but new to Grid, but it's not really true. There's a subtle but very important difference that sometimes shows up, and it will explain the discrepancy here.

In Flexbox, `space-between`, `flex-grow`, etc. divide up the **remaining space** after all items are placed into the area. So if you have a `100px` container with `justify-content: space-between`, as we do here, and three items of sizes `10px`, `40px` and `20px`, Flexbox does the following calculation (roughly):

```python
remainder = available space - total used space
          = 100px - (10px + 20px + 40px) = 100px - 70px
          = 30px
space between items = remainder / (# children - 1)
                    = 30px / (3-1) = 30px / 2
                    = 15px
```    

The resulting layout is:

    10px item | 15px space | 40px item | 15px space | 20px item

And the problem is that centering is not maintained. That 40px item's center point falls at `10px + 15px + (40px/2 = 20px)`, which is `45px`. So it's 5 pixels off from the true center of the container, which is at `50px`.

Grid's `fr` unit is different. It lays out the **total space** given to a grid item *before considering the size of the item*. So if we make our `100px` container a grid-container with `grid-template-columns: 1fr 1fr 1fr`, Grid does the following calculation before it even considers the contents of the grid-items:

```python
remainder = available space - (fixed size column widths)
          = 100px - 0
          = 100px
pixels per fr = remainder / (how many frs used across all column widths)
              = 100px / (1fr + 1fr + 1fr) = 100px / 3
              = 33.333px

So each `1fr` column gets 33.333px, an even third of the space. This means the center of the second column will fall at `33.333px + (half of 33.333px = 16.667px)`, which is a nice round `50px`!

> One very important thing that I totally didn't explain up there is the `fixed size column widths` variable, which in our case is `0`. This is the sum of any columns with values defined in a fixed unit, like `px`, `rem`, `in` (yeah, you can use inches in CSS! Nice for print layouts), or even `%`, which is fixed for the current size of the browser window. So for example if we used the rule `grid-template-columns: 30px 1fr 1fr`, then the calculation changes to this:
>```python
remainder = available space - (fixed size column widths)
          = 100px - 30px
          = 70px
pixels per fr = remainder / (how many frs used across all column widths)
              = 70px / (1fr + 1fr) = 70px / 2
              = 35px
```
> So in this case, each `1fr` column gets 35px.


# Conclusion

Hopefully this gives some more insight into how Flexbox works, and gives someone who's still been holding out a reason to reconsider looking into Grid. It's shocking to me how many people I still encounter who are deeply skeptical of Grid.

As a final note, I want to be clear on something here: I am *not* saying that the `fr` unit is superior to Flexbox's behaviors. There are absolutely cases where Flexbox's behavior is exactly what you want. In many, many cases, it's more important to evenly divide the remaining space among siblings than to keep them all sitting in the same amount of space. Flexbox also allows for a lot more complexity around how items grow and shrink ("flex") based on the container. Maybe I'll write a thing about that at some point. Anyway, my point here is by no means to say that Grid's `fr` units are better than Flexbox, only that they are easier for *this specific case*. I still love Flexbox 💕💕💕



----

# Endnote: tables

*Sigh.* Yes, yes, I know: you can do it with tables. Pretty cleanly, actually, especially if you use CSS tables instead of actual tables:

```css
#controls-body {
  width: 100%;
  display: table;
  table-layout: fixed;
}
p {
  display: table-cell;
}
```

That plus the `text-align` rules gives us another winner:

{% codepen https://codepen.io/kenbellows/pen/JzwxLV default-tab=result %}

And honestly, this would have been much a better solution for me in 2012, even if I had to use actual tables because of limited browser support for CSS tables.

But here's my big problem with this. It's not just the oft-repeated, rarely-explained wisdom to "never use tables for layout" (although, seriously, now that we have Grid, *never use tables for layout*). This really is a decent enough solution in a pre-Grid context; it's clean, it's robust enough to handle adding extra items, and the code is readable, as long as you know what CSS tables are.

No, my problem comes in one word: *responsiveness*. Because it's not responsive, especially with an HTML table, and CSS tables aren't much better    . Sure, you could drop the items into a single column for the mobile view. But suppose you had 4 columns, and wanted a 2x2 layout at an intermediate range. How would you do it with tables? You'd be hard pressed to do it without falling back to absolute positioning shenanigans again.

I won't say it's impossible; I can think of a couple options already, but they're not pretty, and they all have drawbacks. And most importantly, most, if not all, are specific to the current number of items, which makes for brittle code.

Grids, on the other hand, are *amazingly* responsive. It's trivial to redefine a grid's rows and columns, or to place things on a grid differently, so at minimum a media query breakpoint would be easy. (I wrote a whole article raving about Grid Areas, a feature that makes breakpoints that much more useful: [CSS Grid Areas are amazing](https://dev.to/kenbellows/css-grid-areas-are-amazing-1gha).) But in many cases, a breakpoint is unnecessary; Grid was built from the ground up with responsiveness in mind, and has many feature that support it, like [`repeat(auto-fill, ...)`](https://developer.mozilla.org/en-US/docs/Web/CSS/repeat) and [`minmax()`](https://developer.mozilla.org/en-US/docs/Web/CSS/minmax).

For more on all this, I highly recommend checking out Rachel Andrew's [gridbyexample.com](https://gridbyexample.com) and Jen Simmons' [Layout Land](https://www.youtube.com/channel/UC7TizprGknbDalbHplROtag).