Going all-in on Flexbox

On a recent project we finally got to use Flexbox extensively for page layout. In the interests of increasing general knowledge about flexbox (including mine), I’ll explain a number of layouts that use flexbox extensively, organised in 3 sections:

  1. Main page layout (for a single-page JavaScript application)
  2. Fluid product grid and accordion-like summary box
  3. Product “cards” and “slabs”

[Edit 12 September: See Reddit for a discussion of this post]

Preamble

This is not a from-basics flexbox tutorial. Here are two good ones. But in short, you create a flexbox, or make a flex container, by giving an element display: flex. Its immediate children then become flex items, on which you can set the flex property. They can themselves be flex containers.

Browser support: IE <11 is out, as are old versions of Mobile Safari. For this project that was OK, and we were able to use flexbox properties without vendor prefixes.

Disclaimer: I’m not claiming this is the perfect way to do it, so copy it at your own risk. But it seems to work very well for us. Questions or suggestions are welcome!

1. Main page layout

Screenshot of application layoutThis is a single-page JavaScript application, with a header toolbar, footer toolbar, and a canvas area in the middle with optional resizable panes on either side.

Everything is surrounded by .wrapper, which is position: fixed to the exact size of the viewport. This is set to be our flex container with display: flex; flex-direction: column; so the flex items go vertically.

Diagram showing HTML structure and flexbox propertiesA. .body-wrapper gets flex: 1. This makes it stretch to fill the available space. (It’s shorthand for flex: 1 1 0, so flex-grow is 1 (it can grow), flex-shrink is 1 (it can shrink), and flex-basis is 0 (it has no minimum height).

B. Each flex item – .header-wrapper, .body-wrapper and .footer-wrapper – is also set to be a flex container. Their flex items are horizontal though, so they’re all flex-direction: row (which is the default). Note the use of align-items: center which vertically aligns the header and footer contents regardless of their height. (Comparable to vertical-align in tables.)

In .header-wrapper and .body-wrapper there’s also one element each with flex: 1 so that it stretches to fill the available space. In .footer-wrapper we need to set each item to flex: 1 1 auto (changing flex-basis to auto) so that they can protrude into each other’s half and not start wrapping prematurely).

Note how nice it is that we can have any number of left or right panes (.pane-wrapper), of any size, and .canvas-wrapper will just adapt. We could also use order to change the order panes display in if desired.

C. .pane-wrapper and .canvas-wrapper are each set as flex containers with flex-direction: column (they have a titlebar and body area each). .pane-body is a flex-container with flex-direction: row. Strictly speaking flexbox is unnecessary for .canvas-wrapper and .pane-body as we ended up setting .pane-content and .canvas to be position: absolute. This was so they don’t affect the calculation of the outer containers’ heights at all, and each can scroll. It didn’t look like this was possible if .pane-content and .canvas were flex items.

How would we have done this layout without Flexbox? On a previous project with very similar layout, we placed every pane using position: fixed. This has the advantage of excellent support in old browsers. But it comes at the cost of fixed positions in the CSS, with classes for .with-header, .with-footer, .with-titlebar, .with-sidebar, and so on, all toggled with JavaScript, each having different positions set in the CSS. If you change one of those values, you usually have to change some others. And each of those panes can’t change in size because of their contents.

Also, making things vertically aligned within rows would have been impossible without some nasty hacks.

2. Product grid and summary box

Screenshot of product grid and summary boxFlexbox was ideal for (A) fluid product grids, as well as (B) an accordion-like summary box. This interface is inside a modal popup of fluid width. To maximise screen real estate it resizes smoothly in proportion with the window width and height. (Using Bootstrap modal markup, .modal.modal-content and .modal-body are all given percentile heights.) Firstly the flex container .col-wrapper arranges columns horizontally with flex-direction: row. Note that align-items: stretch makes each column the same height. And because it is used inside of a container with known height, height: 100% makes it stretch to the full height available. .col-content gets flex: 1 so that it stretches to fill the horizontal space available, and both .col-facets and .col-summary are given a width and prevented from flexing with flex: 0 0 <width>. Both .col-facets and .col-content have overflow: auto so they can scroll.

Diagram showing HTML structure and flexbox properties of product grid and summary boxNone of the above would’ve been very hard without flexbox. But the next two things would’ve.

A. Many parts of the app uses a grid of products, either in “card” or “slab” form (described in more detail next section). These are stretchy, adapting their width to the fluid container width to fill rows exactly. But we also use media queries to control the number of columns we allow at any screen size.

The flex container is .product-grid. It has flex-wrap: wrap to allow wrapping, and align-items: stretch makes every item in a row the same height. .product is the flex item. Products have 1% left/right margins, equivalent to 2% between each. (The container also has 1% left/right padding.) .product is sized with percentile values using flex-basis, with a Less formula based on the number of columns for that screen size: flex: 0 0 (100 / 3) - 2% (for 3 columns, taking into account 2% margin). You can see some examples of the media queries in the illustration above.

Without flexbox? Grid items would’ve been floated, but there would’ve been no way to equalise heights within a row without very ugly hacks.

Is this the best way to do a fluid grid with flexbox? This method may seem unsophisticated, relying entirely on percentile flex-basis, and not on flex-grow or flex-shrink. It may seem not much better than a float-based approach, although we do get the advantage of consistent box heights. The reason is to align items in the last row to the grid. If the last row is not completely filled, most flexbox grids fill the space by allowing the items to grow in width (example), or increasing the space between them. Neither of those had acceptable results in our case. Here is a codepen with our grid in its minimal form. Our approach also gives us a maximum number of columns for every screen size breakpoint.

Screenshot of summary box in short and tall versionsB. The challenge in the summary box was to have it fill the vertical space, but have a single section .summary-content that stretches to fill the available space, yet starts scrolling when the space is filled. .summary-box needs to have its height set to 100% (its parents also having height specified), and .summary-content needs flex-grow: 1 and flex-shrink: 1 so it can both grow and shrink as needed. overflow: auto lets it scroll.

Without flexbox, this is pretty much impossible, if the other rows in the box have unpredictable heights (which many of them do).

3. Product cards and slabs

In product grids, products were either represented as large “cards” with image and description, or compact “slabs”. Flexbox was extremely helpful in each case.

Diagram showing HTML structure and flexbox properties of product cards and slabsA. For .product-card, we have a vertical flexbox with a single element .product-desc that stretches to fill the available space. If a max-height is specified, .product-desc would be truncated. We’re considering putting the image above the title, and it’s to know we could do that using order without changing the HTML if we wanted to.

Again, without flexbox, this sort of thing is impossible if the other rows have unpredictable heights.

B. For .product-slab we needed the contents aligned to the left and right edges. Without flexbox, this would’ve been a pain, requiring floats and clearfixing, and no way to distribute the horizontal space according to the amount of content. With flexbox, we can.

flex-direction: row arranges the contents of the slab horizontally. justify-content: space-between pushes the title and content against the opposite edges. And flex-grow: 1 on .product-title ensures the horizontal space is filled inside, pushing it against the left-hand edge.

Summary

This project was a valuable learning experience. Flexbox can be surprisingly difficult to get your head around, and the best way to learn is to use it in earnest. While most projects can’t yet rely on flexbox to this degree, for browser support reasons, it can still prove useful for many elements, especially with some fallback measures – this presentation by Zoe Gillenwater has many examples. We’ll definitely be using it more in future projects.

To keep this post to a manageable length I tried to be as concise as possible, so if there are any explanations that are unclear, or if there are things you’d have done differently, let me know in the comments.

13 thoughts on “Going all-in on Flexbox

    1. Francois Jordaan Post author

      Hi Sam. Fortunately that issue didn’t affect us, but thanks for the link – I wasn’t aware of it before.

    2. Francois Jordaan Post author

      I wrote too soon, Sam. I was having trouble with IE11 minimising the heights of the cards, and realised it is related to the issue you link to. Setting flex: 1 1 auto instead of flex: 1 on .product-desc fixes it. (Should be equivalent according to the spec, but not in IE11.) Thanks!

      1. Sam Storie

        Good to know about that setting. I gained many gray hairs trying to figure out why vanilla flex box was having so many cross-browser issues. This stuff can be maddening!

    1. Francois Jordaan Post author

      Hi Šime – I really struggled to remember why I did this. I’m usually very good at commenting my source code but didn’t in this case – let that be a lesson! I knew it was a late change, since (as you point out) height:100% works fine.
      The commit message sheds some light on the matter: “change .wrapper to position:fixed – fixes iPad layout and scroll issue”. So as I suspected it was a browser compatibility workaround for (in this case) Mobile Safari. I’m afraid I can’t be more specific, as I can’t seem to replicate the issue right now.

  1. Ben Dunkle

    Excellent article. Thanks for this. I’ve been using flexbox for layout almost exclusively, and the practices listed here are sure to solve some hurdles I’ve encountered. Align-items:stretch sounds great, since I’ve been using min-height to make .product’s in ul.product-grid the same height.

    1. Francois Jordaan Post author

      Yep, align-items:stretch is the bomb. You probably still want to use min-height, and possibly max-height as well.

    1. Francois Jordaan Post author

      If I’m spoon feeding it’s to my future self! I wanted to make sure I understand and have good documentation of what we were doing here.

  2. Pingback: Collective #245 | Latest News and RSS Feeds

Comments are closed.