Home » Uncategorized » Flex box model

Flex box model

Categories

So after watching most of the talks at W3Conf the last 2 days, it became appearant that the web is moving forward with a big pace. Lots of new features and technologies are being implemented by browsers, and soon webdevelopment will be different from what we are used to in the present day.

One of the new things that sparked my interest was the new CSS Flexible Box Layout Module as layed out in the talk (notes by Chris Coyier) by Eric Meyer, so I decided to play around with it a bit.

The base HTML to test with is more or less like a webshop. It contains a header, a footer, and main part that is divided in a navigation part, the real content, and an extra bar that could be used for something like an overview for a shoppingcart.

<!DOCTYPE html>
<html>
 <head>
 <meta charset="utf-8">
 <title>My awesome webshop using flex</title>
 </head>
 <body>
 <header>
 <h1>My awesome webshop using flex</h1>
 </header>
 <main>
 <nav>
 <h3>Categories</h3>
 <ul>
 <li>Category 1
 <li>Category 2
 <li>Category 3
 <li>Category 4
 </ul>
 </nav>
 <article id="products">
 <section class="product">
 <h2>Product title</h2>
 <p>Product description, lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus id eros odio, sed varius lorem. Nunc a eleifend magna. Cras eleifend nibh non augue tempus dapibus. Nam dignissim, velit congue porta vulputate, lectus nulla dignissim libero, sit amet suscipit sem tortor nec leo. Curabitur suscipit consectetur lectus vel ultricies.</p>
 <img src="http://www.yarnsandfibers.com/preferredsupplier/images/dyes/2.jpg" alt="Product title" />
 <aside class="buynow">
 <button>Buy now</button>
 </aside>
 </section>
 <section class="product">
 <h2>Product title</h2>
 <p>Product description, lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus id eros odio, sed varius lorem. Nunc a eleifend magna. Cras eleifend nibh non augue tempus dapibus. Nam dignissim, velit congue porta vulputate, lectus nulla dignissim libero, sit amet suscipit sem tortor nec leo.</p>
 <img src="http://www.yarnsandfibers.com/preferredsupplier/images/dyes/2.jpg" alt="Product title" />
 <aside class="buynow">
 <button>Buy now</button>
 </aside>
 </section>
 <section class="product">
 <h2>Product title</h2>
 <p>Product description, lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus id eros odio, sed varius lorem. Nunc a eleifend magna. Cras eleifend nibh non augue tempus dapibus.</p>
 <img src="http://www.yarnsandfibers.com/preferredsupplier/images/dyes/2.jpg" alt="Product title" />
 <aside class="buynow">
 <button>Buy now</button>
 </aside>
 </section>
 <section class="product">
 <h2>Product title</h2>
 <p>Product description, lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus id eros odio, sed varius lorem. Nunc a eleifend magna. Cras eleifend nibh non augue tempus dapibus. Nam dignissim, velit congue porta vulputate, lectus nulla dignissim libero, sit amet suscipit sem tortor nec leo. Curabitur suscipit consectetur lectus vel ultricies. Product description, lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus id eros odio, sed varius lorem.</p>
 <img src="http://www.yarnsandfibers.com/preferredsupplier/images/dyes/2.jpg" alt="Product title" />
 <aside class="buynow">
 <button>Buy now</button>
 </aside>
 </section>
 <section class="product">
 <h2>Product title</h2>
 <p>Product description, lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus id eros odio, sed varius lorem. Nunc a eleifend magna. Cras eleifend nibh non augue tempus dapibus. Nam dignissim, velit congue porta vulputate, lectus nulla dignissim libero, sit amet suscipit sem tortor nec leo. Curabitur suscipit consectetur lectus vel ultricies.</p>
 <img src="http://www.yarnsandfibers.com/preferredsupplier/images/dyes/2.jpg" alt="Product title" />
 <aside class="buynow">
 <button>Buy now</button>
 </aside>
 </section>
 <section class="product">
 <h2>Product title</h2>
 <p>Product description, lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus id eros odio, sed varius lorem. Nunc a eleifend magna. Cras eleifend nibh non augue tempus dapibus. Nam dignissim, velit congue porta vulputate, lectus nulla dignissim libero, sit amet suscipit sem tortor nec leo. Curabitur suscipit consectetur lectus vel ultricies.</p>
 <img src="http://www.yarnsandfibers.com/preferredsupplier/images/dyes/2.jpg" alt="Product title" />
 <aside class="buynow">
 <button>Buy now</button>
 </aside>
 </section>
 <section class="product">
 <h2>Product title</h2>
 <p>Product description, lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus id eros odio, sed varius lorem. Nunc a eleifend magna. Cras eleifend nibh non augue tempus dapibus. Nam dignissim, velit congue porta vulputate, lectus nulla dignissim libero, sit amet suscipit sem tortor nec leo. Curabitur suscipit consectetur lectus vel ultricies.</p>
 <img src="http://www.yarnsandfibers.com/preferredsupplier/images/dyes/2.jpg" alt="Product title" />
 <aside class="buynow">
 <button>Buy now</button>
 </aside>
 </section>
 <section class="product">
 <h2>Product title</h2>
 <p>Product description, lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus id eros odio, sed varius lorem. Nunc a eleifend magna. Cras eleifend nibh non augue tempus dapibus. Nam dignissim, velit congue porta vulputate, lectus nulla dignissim libero, sit amet suscipit sem tortor nec leo. Curabitur suscipit consectetur lectus vel ultricies.</p>
 <img src="http://www.yarnsandfibers.com/preferredsupplier/images/dyes/2.jpg" alt="Product title" />
 <aside class="buynow">
 <button>Buy now</button>
 </aside>
 </section>
 <section class="product">
 <h2>Product title</h2>
 <p>Product description, lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus id eros odio, sed varius lorem. Nunc a eleifend magna. Cras eleifend nibh non augue tempus dapibus. Nam dignissim, velit congue porta vulputate, lectus nulla dignissim libero, sit amet suscipit sem tortor nec leo. Curabitur suscipit consectetur lectus vel ultricies.</p>
 <img src="http://www.yarnsandfibers.com/preferredsupplier/images/dyes/2.jpg" alt="Product title" />
 <aside class="buynow">
 <button>Buy now</button>
 </aside>
 </section>
 </article>
 <aside>
 <h3>Cart contents</h3>
 <ul>
 <li>Product 1
 <li>Product 2
 <li>Product 3
 <li>Product 4
 </ul>
 </aside>
 </main>
 <footer>
 Flex-model test
 </footer>
 </body>
</html>

Without CSS the above sourcecode already gives a pretty overview of what the page should look like. For screenreaders and mobile devices this is already pretty much what you’d expect (Of course, you would want some more styling for mobile devices). However, this doesn’t look too good in a desktop browser. So let’s try and use the flex box model to lay this out.

By adding


main {
 display: -ms-flex;
 display: -webkit-flex;
 display: flex;
}

we see (in Chrome Canary, which I use to develop) that the three elements within the main element immediately get positioned next to each other, instead of underneath each other. Note that vendor-prefixes are still in place for both IE (10) and Chrome.

Because none of the three elements (nav, article, aside) have been given sizes, the browser will try to determine the best widths itself. In this case the nav and aside are too small, so let’s give those a fixed width:


main {
 display: -ms-flex;
 display: -webkit-flex;
 display: flex;
}

main > nav {
 width: 200px;
}

main > aside {
 width: 200px;
}

This gives the navigation and shoppingcart some more ‘breathing room’. Now the next thing we want to achieve is laying out the products next to each other, where the will ‘flip’ to a new row whenever the end of the row is reached. With the flex box model this is pretty easy. First, let’s make the article behave as a flex box as well:


main {
 display: -ms-flex;
 display: -webkit-flex;
 display: flex;
}

main > nav {
 width: 200px;
}

main > aside {
 width: 200px;
}

#products {
 display: -ms-flex;
 display: -webkit-flex;
 display: flex;
}

This lays out the products next to each other as well. However, the default-behaviour is to keep laying out the items next to each other until we run out of items. In order to make the items wrap around to a next row (or column in case of a vertical lay-out) you set the flow of the flex box:


main {
 display: -ms-flex;
 display: -webkit-flex;
 display: flex;
}

main > nav {
 width: 200px;
}

main > aside {
 width: 200px;
}

#products {
 display: -ms-flex;
 display: -webkit-flex;
 display: flex;
 -ms-flex-flow: row wrap;
 -webkit-flex-flow: row wrap;
 flex-flow: row wrap;
}

Uhoh, a problem! Suddenly all of the products are displayed underneath each other again instead of next to each other. This is because the width of each product will be as much as is needed by the content. Because the descriptions of the products are about a paragraph long, this means the products will be as wide as they can, so each product will contain a complete row. By giving each product a specific width, or a min- and max-width, we can counteract this:


main {
 display: -ms-flex;
 display: -webkit-flex;
 display: flex;
}

main > nav {
 width: 200px;
}

main > aside {
 width: 200px;
}

#products {
 display: -ms-flex;
 display: -webkit-flex;
 display: flex;
 -ms-flex-flow: row wrap;
 -webkit-flex-flow: row wrap;
 flex-flow: row wrap;
}
.product {
 width: 250px;
}

Now we see that the products get nicely distributed over several rows. By changing the width of your browserwindow (viewport) you can see that the number of products on each row will vary, depending on the available space.

Because the combined width of the products will usually not be exactly the available width, you will be having whitespace between the last product on a row, and the aside that contains the content of the shoppingcart. Let’s distribute this whitespace around the products instead of letting it sit there. This can be done through the justify-content property:


main {
 display: -ms-flex;
 display: -webkit-flex;
 display: flex;
}

main > nav {
 width: 200px;
}

main > aside {
 width: 200px;
}

#products {
 display: -ms-flex;
 display: -webkit-flex;
 display: flex;
 -ms-flex-flow: row wrap;
 -webkit-flex-flow: row wrap;
 flex-flow: row wrap;
 -ms-justify-content: space-around;
 -webkit-justify-content: space-around;
 justify-content: space-around;
}
.product {
 width: 250px;
}

This way the ‘unused’ space gets spread around the products.

Even though all the products are the same height now (a very handy thing flex box does automagically), the content of these boxes still make the ‘buy now’ buttons go all over the place, as the descriptions aren’t all the same length.

By making each product a vertical flex box itself, we can easily fix this by using ‘margin-top: auto;’ on the element we want to push to the bottom:


main {
 display: -ms-flex;
 display: -webkit-flex;
 display: flex;
}

main > nav {
 width: 200px;
}

main > aside {
 width: 200px;
}

#products {
 display: -ms-flex;
 display: -webkit-flex;
 display: flex;
 -ms-flex-flow: row wrap;
 -webkit-flex-flow: row wrap;
 flex-flow: row wrap;
 -ms-justify-content: space-around;
 -webkit-justify-content: space-around;
 justify-content: space-around;
}
.product {
 width: 250px;
 display: -ms-flex;
 display: -webkit-flex;
 display: flex;
 -ms-flex-flow: column;
 -webkit-flex-flow: column;
 flex-flow: column;
}
.product > .buynow {
 margin-top: auto;
}

However, because we made the product a flex box as well, the productimage gets stretched to the width of the container as well. Since we also want the image to be centered within the product-item, we can fix both with the align-self property:


main {
 display: -ms-flex;
 display: -webkit-flex;
 display: flex;
}

main > nav {
 width: 200px;
}

main > aside {
 width: 200px;
}

#products {
 display: -ms-flex;
 display: -webkit-flex;
 display: flex;
 -ms-flex-flow: row wrap;
 -webkit-flex-flow: row wrap;
 flex-flow: row wrap;
 -ms-justify-content: space-around;
 -webkit-justify-content: space-around;
 justify-content: space-around;
}
.product {
 width: 250px;
 display: -ms-flex;
 display: -webkit-flex;
 display: flex;
 -ms-flex-flow: column;
 -webkit-flex-flow: column;
 flex-flow: column;
}
.product > .buynow {
 margin-top: auto;
}
.product > img {
 -ms-align-self: center;
 -webkit-align-self: center;
 align-self: center;
}

And, since we made the product a flex box column, we can also easily change the order in which the parts are displayed. As we won’t have to change the source-code to achieve this, this has no effect on screenreaders etc.. So let’s move the image right under the product-name:


main {
 display: -ms-flex;
 display: -webkit-flex;
 display: flex;
}

main > nav {
 width: 200px;
}

main > aside {
 width: 200px;
}

#products {
 display: -ms-flex;
 display: -webkit-flex;
 display: flex;
 -ms-flex-flow: row wrap;
 -webkit-flex-flow: row wrap;
 flex-flow: row wrap;
 -ms-justify-content: space-around;
 -webkit-justify-content: space-around;
 justify-content: space-around;
}
.product {
 width: 250px;
 display: -ms-flex;
 display: -webkit-flex;
 display: flex;
 -ms-flex-flow: column;
 -webkit-flex-flow: column;
 flex-flow: column;
}
.product > .buynow {
 margin-top: auto;
}
.product > h2 {
 -ms-order: -1;
 -webkit-order: -1;
 order: -1;
}
.product > img {
 -ms-align-self: center;
 -webkit-align-self: center;
 align-self: center;
 -ms-order: -1;
 -webkit-order: -1;
 order: -1;
}

The default order for all items is 0. By giving an item an order of -1, we put it to the front. By first giving the title an order of -1, and then the image also an order of -1, we place the title first, the image second, and all other items after those two, in the order they are found in the sourcecode.

Now we have a pretty decent base for browsers that support the flex box model, let’s see if we can make the same code work in browsers that don’t support it (for example: IE9 and below). For this we have to resort to one of our old hacks: using floats.

Because the 3 main elements (nav, main and the aside with the cart) are already in the correct order, let’s see what happens if we add floats to these elements:


main {
 display: -ms-flex;
 display: -webkit-flex;
 display: flex;
}

main > nav {
 width: 200px;
 float: left;
}

main > aside {
 width: 200px;
 float: right;
}

#products {
 display: -ms-flex;
 display: -webkit-flex;
 display: flex;
 -ms-flex-flow: row wrap;
 -webkit-flex-flow: row wrap;
 flex-flow: row wrap;
 -ms-justify-content: space-around;
 -webkit-justify-content: space-around;
 justify-content: space-around;
 float: left;
}
.product {
 width: 250px;
 display: -ms-flex;
 display: -webkit-flex;
 display: flex;
 -ms-flex-flow: column;
 -webkit-flex-flow: column;
 flex-flow: column;
}
.product > .buynow {
 margin-top: auto;
}
.product > h2 {
 -ms-order: -1;
 -webkit-order: -1;
 order: -1;
}
.product > img {
 -ms-align-self: center;
 -webkit-align-self: center;
 align-self: center;
 -ms-order: -1;
 -webkit-order: -1;
 order: -1;
}

footer {
 clear: both;
}

Besides the floats on those 3 elements, a ‘clear: both;’ has been added to the footer, to keep the footer below these elements. This neatly places these 3 elements next to each other. It has no effect on the browsers that support the flex box model because floats get ignored when using that model (Hooray!). The products are still underneath each other, however. Let’s see if a simple float on the products will solve this:


main {
 display: -ms-flex;
 display: -webkit-flex;
 display: flex;
}

main > nav {
 width: 200px;
 float: left;
}

main > aside {
 width: 200px;
 float: right;
}

#products {
 display: -ms-flex;
 display: -webkit-flex;
 display: flex;
 -ms-flex-flow: row wrap;
 -webkit-flex-flow: row wrap;
 flex-flow: row wrap;
 -ms-justify-content: space-around;
 -webkit-justify-content: space-around;
 justify-content: space-around;
 float: left;
}
.product {
 width: 250px;
 display: -ms-flex;
 display: -webkit-flex;
 display: flex;
 -ms-flex-flow: column;
 -webkit-flex-flow: column;
 flex-flow: column;
 float: left;
}
.product > .buynow {
 margin-top: auto;
}
.product > h2 {
 -ms-order: -1;
 -webkit-order: -1;
 order: -1;
}
.product > img {
 -ms-align-self: center;
 -webkit-align-self: center;
 align-self: center;
 -ms-order: -1;
 -webkit-order: -1;
 order: -1;
}

footer {
 clear: both;
}

Almost, but this makes the container for the products the width of the viewport again. We can make the container a fixed width (say, 1000px, which equals 4 products), but that would influence the lay-out in the flex box model as well. Luckily we can use the flex property to mark an item as being flexible, even though it has a given size:


main {
 display: -ms-flex;
 display: -webkit-flex;
 display: flex;
}

main > nav {
 width: 200px;
 float: left;
}

main > aside {
 width: 200px;
 float: right;
}

#products {
 display: -ms-flex;
 display: -webkit-flex;
 display: flex;
 -ms-flex-flow: row wrap;
 -webkit-flex-flow: row wrap;
 flex-flow: row wrap;
 -ms-justify-content: space-around;
 -webkit-justify-content: space-around;
 justify-content: space-around;
 float: left;
 width: 1000px;
 -ms-flex: 1;
 -webkit-flex: 1;
 flex: 1;
}
.product {
 width: 250px;
 display: -ms-flex;
 display: -webkit-flex;
 display: flex;
 -ms-flex-flow: column;
 -webkit-flex-flow: column;
 flex-flow: column;
 float: left;
}
.product > .buynow {
 margin-top: auto;
}
.product > h2 {
 -ms-order: -1;
 -webkit-order: -1;
 order: -1;
}
.product > img {
 -ms-align-self: center;
 -webkit-align-self: center;
 align-self: center;
 -ms-order: -1;
 -webkit-order: -1;
 order: -1;
}

footer {
 clear: both;
}

This lets the flex box model behave as it was before, while we can have control over the size of the element in browsers that don’t support the flex box model yet.

Even though we can’t do neat things such as distributing the remaining whitespace or give all items the same height (without explicitly giving it a height in css), this way you can create a reasonable fallback for older browsers.

The final document (HTML + CSS) is also available at my codepen.

Resources/further reading:

W3C Spec
Compatibility tables at caniuse.com

Notes:

I only covered the current spec here. The spec has undergone lots of changes, so when googling you’re bound to find articles that describe the old spec. For more info about this, read this article by Chris Coyier.
Mozilla currently only supports the old spec. They are working on incorperating the new spec though. Besides, at the moment firefox will only use the flex box model after the user has toggled the appropriate flag in the config.

Edit: after publishing I made some more changes to make the code behave better, which is documented in the flex box model – revisited post.


1 Comment

  1. r12van says:

    Reblogged this on Rizvan menulis and commented:
    flexbox-model

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: