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:
- Main page layout (for a single-page JavaScript application)
- Fluid product grid and accordion-like summary box
- 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
This 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.
A.
.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
Flexbox 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.
None 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.
B. 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.
A. 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.
Did you find any need to specify min-height or min-width values on any flex containers? I ran into an issue across browsers where child items would “squish” in IE without them – https://css-tricks.com/boxes-fill-height-dont-squish/.
Hi Sam. Fortunately that issue didn’t affect us, but thanks for the link – I wasn’t aware of it before.
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 offlex: 1
on.product-desc
fixes it. (Should be equivalent according to the spec, but not in IE11.) Thanks!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!
Why fixed positioning on the wrapper? Setting height to 100% seems to work fine: https://output.jsbin.com/zepegu/quiet.
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.
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.
Yep,
align-items:stretch
is the bomb. You probably still want to usemin-height
, and possiblymax-height
as well.Wow. Amazing article. You said its not a basic tutorial, but believe me you are spoon feeding.
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.
Pingback: Collective #245 | Latest News and RSS Feeds
Excellent tutorial, the illustrations really helped. For beginners new to Flexbox, another tutorial I recommend reading first is CSS Flexbox 101: http://www.javascriptkit.com/dhtmltutors/css-flexbox.shtml
There’s only one problem with flexbox: once you start using, you never want to go back to anything else.
Just an advice, if you support IE11, don’t use unitless flex-basis as the rule could be ignored. Replace it by 0%. (Not sure if this is still a bug but…)
Source: https://github.com/philipwalton/flexbugs#4-flex-shorthand-declarations-with-unitless-flex-basis-values-are-ignored