CSS3 Flexible Box Layout Explained
The flexible box layout module — or “flexbox,” to use its popular nickname — is an interesting part of the W3C Working Draft. The flexbox specification is still a draft and subject to change, so keep your eyes on the W3C, but it is part of a new arsenal of properties that will revolutionize how we lay out pages. At least it will be when cross-browser support catches up.
In the meantime, we can experiment with flexbox and even use it on production websites where fallbacks will still render the page correctly. It may be a little while until we consider it as mainstream as, say,
border-radius
, but our job is to investigate new technologies and use them where possible. That said, when it comes to something as fundamental as page layout, we need to tread carefully.The Display Property
So what is flexbox, and why was it created? First, let’s look at how we currently lay out pages and some of the problems with that model.Until last year, most of us were using tables to lay out our pages. Okay, maybe not last year! But I suspect that many of you reading this have been guilty of relying on tables at some point in your career. At the same time, it actually made a lot of sense. And let’s face it: it worked… to a point. However, we all then faced the reality that tables were semantically dubious and incredibly inflexible. And through the haze of this mark-up hangover, we caught a glimpse of the future: the CSS box model. Hurray!
The CSS box model allowed us to tell the browser how to display a piece of content, and in particular how to display it as a box. We floated left and right, we tried to understand what
inline-block
meant, and we read countless articles about clearfix
, before just copying and pasting the clearfix
hack-du-jour into our CSS.For those of us testing our websites back to IE6, we had to grapple with
hasLayout
and triggering it with the following or some similar fix:* html #element { height: 1%; }The box model worked, and in most cases it worked well. But as the Web entered its teenage years, it demanded more complex ways of laying out content and — thanks to a certain Mr. Ethan Marcotte — of responding to the size of the browser and/or device.
Percentage + Padding + Border = Trouble
Here’s another problem with the current box model: absolute values for padding, margin and border all affect the width of a box. Take the following:#element { width: 50%; border 1px solid #000; padding: 0 5px; }This will not give us a box that is 50% of its parent. It will actually render an element that is 50% of the parent’s width plus 12 pixels (2-pixel border + 10-pixel padding). You could set the padding as a percentage value (although not for input elements in Firefox!), but adding percentage values of widths to the pixel values of borders can cause mathematical problems.
There are two ways to fix this problem. The first is to use the new CSS3
box-sizing
property, and setting it to border-box
:#element { box-sizing: border-box; width: 50%; border 1px solid #000; padding: 0 5px; }This new CSS3 panacea effectively tells the browser to render the element at the specified width, including the border width and padding.
The second way to fix this problem is to use flexbox.
Many Problems, Many Solutions
The W3C responded with a suite of answers: the flexible box model, columns, templates, positioned floats and the grid. Adobe added regions to the mix, but they are not yet supported by any browser.The
display
property already has no less than a staggering 16 values: inline
, block
, list-item
, inline-block
, table
, inline-table
, table-row-group
, table-header-group
, table-footer-group
, table-row
, table-column-group
, table-column
, table-cell
, table-caption
, none
and inherit
.And now we can add a 17th:
box
.Living In A Box
Let’s take a look at flexbox, which brings with it a brand new value for the display property (box
) and no less than 8 new properties. Here’s how the W3C defines the new module:
In this new box model, the children of a box are laid out either horizontally or vertically, and unused space can be assigned to a particular child or distributed among the children by assignment of flex
to the children that should expand. Nesting of these boxes (horizontal inside vertical, or vertical inside horizontal) can be used to build layouts in two dimensions.
Sounds exciting! The Working Draft expands on this a little:Flexbox… lacks many of the more complex text or document-formatting properties that can be used in block layout, such as “float” and “columns,” but in return it gains more simple and powerful tools for aligning its contents in ways that Web apps and complex Web pages often need.Now this is beginning to sound interesting. The flexbox model picks up where the box model leaves off, and the W3C reveals its motivation by noting that “Web apps and complex Web pages” need a better layout model. Here’s a list of the new flexbox properties:
box-orient
,box-pack
,box-align
,box-flex
,box-flex-group
,box-ordinal-group
,box-direction
,box-lines
.
You might also want to check out Prefixr from Jeffrey Way, which can help generate some of the CSS for you. However, I found that it incorrectly generated the
display: box
property, so check all of its code.Everything Will Change
If you take the time to read or even browse the latest Working Draft (from 22 March 2011), you’ll notice a lot of red ink, and with good reason. This spec has issues and is still changing; we are in unchartered waters.It’s worth noting that the syntax used in this article, and by all current browsers, is already out of date. The Working Draft has undergone changes to much of the syntax used in the flexbox model. For example:
display: box;This will become:
display: flexbox;Other changes include some properties being split (
box-flex
will become flex-grow
and flex-shrink
), while others will be combined (box-orient
and box-direction
will become flex-direction
). Indeed, anything that starts box-
will be changed to flex-
. So, keep your eyes on the spec and on browser implementations. (CanIUse helps, but it doesn’t cover all of the properties.)PaRappa the Wrapper
Using flexbox often requires an extra div or two, because the parent of any flexbox element needs to havedisplay
set to box
. Before, you could get away with the following:<div style="float: left; width: 250px;"> Content here </div> <div style="float: right; width: 250px;"> Content here </div>Now with flexbox, you’ll need:
<div style="display: box"> <div style="width: 250px"> Content here </div> <div style="width: 250px"> Content here </div> </div>Many of you have already turned away, insulted by this extra mark-up that is purely for presentation. That’s understandable. But here’s the thing: once you master the CSS, this extra containing div becomes a small price to pay. Indeed, you’ll often already have a containing element (not necessarily a div) to add
display: box
to, so there won’t be a trade-off at all.On a broader note, sometimes you need presentational mark-up. It’s just the way it goes. I’ve found that, particularly when working on cross-browser support for a page, I have to add presentational mark-up for browsers such as IE6. I’m not saying to contract “div-itis,” but because we all use HTML5 elements in our mark-up, we find that sections often need div containers. That’s fine, as long as it’s kept to a minimum.
With this in mind, let’s get busy with some code. I’ve put together a demo page, and you can download all of the source files.
Over the next few paragraphs, we’ll use the new flexbox model to create a basic home page for a blog. You might want to launch a latest-generation browser, though, because we’re now coding at the cutting edge. And it’s an exciting place to be.
box-flex
Let’s start with the basics:box-flex
. Without box-flex
, very little can be achieved. Simply put, it tells the browser how to resize an element when the element is too big or small for its parent.Consider the following classic problem. You have a container with three children that you want to position side by side. In other words, you float them left. If the total width of these boxes is wider than that of the parent — perhaps because of padding, margin or a border — then you need to either specify exact widths in pixels (which is not flexible) or work in percentages (and the sometimes mind-bending calculations that come with them!).
Here’s the problem we have on our Fruit Blog, with three 320-pixel-wide asides (plus padding and margin) inside a 920-pixel-wide container:
As you can see, the content is wider than the parent. However, if we set set the parent to
display: box
and each of these asides to box-flex: 1
, then the browser takes care of the math and renders the following:So, what exactly has happened here?
The
box-flex
property refers to how the browser will treat the width of the box — or, more specifically, the unused space (even if that space is negative — i.e. even if the rendered boxes are too big for the container) — after the box has rendered. The value (1
in our example) is the ratio. So, with each aside set to a ratio of 1
, each box is scaled in exactly the same way.In the first instance, each aside was 320 pixels + 20 pixels of padding on the left and right. This gave us a total width of 360 pixels; and for three asides, the width was 1080 pixels. This is 160 pixels wider than the parent container.
Telling the browser that each box is flexible (with
box-flex
) will make it shrink the width of each box — i.e. it will not change the padding. This calculation is a fairly easy one:160 pixels ÷ 3 asides = 53.333 pixels to be taken off each aside.And, if we look in Chrome Developer tools, we will see this is exactly how wide the box now is (rounded up to the nearest decimal):
320 pixels – 53.333 = 266.667 pixels
The same would be true if each aside had a width of 100 pixels. The browser would expand each element until it filled the unused space, which again would result in each aside having a width of 266.667 pixels.
This is invaluable for flexible layouts, Because it means that your padding, margin and border values will always be honored; the browser will simply change the width of the elements until they fit the parent. If the parent changes in size, so will the flexible boxes within it.
Of course, you can set
box-flex
to a different number on each element, thus creating different ratios. Let’s say you have three elements side by side, each 100 pixels wide, with 20 pixels padding, inside a 920-pixel container. It looks something like this:Now, let’s set the
box-flex
ratios:.box1 { box-flex: 2; } .box2 { box-flex: 1; } .box3 { box-flex: 1; }Here’s what it looks like:
What just happened?!
Well, each aside started off as 140-pixels wide (100 pixels + 40 pixels padding), or 420 pixels in total. This means that 500 pixels were left to fill once we’d made them flexible boxes.
However, rather than split the 500 pixels three ways, we told the browser to assign the first aside with a
box-flex
of 2
. This would grow it by 2 pixels for every 1 pixel that the other two boxes grow, until the parent is full.Perhaps the best way to think of this is that our ratio is 2:1:1. So, the first element will take up 2/4 of the unused space, while the other two elements will take up 1/4 of the unused space (2/4 + 1/4 + 1/4 = 1).
2/4 of 500 pixels is 250, and 1/4 is 125 pixels. The final widths, therefore, end up as:
Add all of these values up and you reach the magic number of 920 pixels, the width of our parent..box1
= 350px (100px + 250px) + 40px padding
.box2
= 225px (100px + 125px) + 40px padding
.box3
= 225px (100px + 125px) + 40px padding
An important distinction to make is that the ratio refers to how the additional pixels (or unused space) are calculated, not the widths of the boxes themselves. This is why the widths are 350:225:225 pixels, and not 460:230:230 pixels.
The wonderful thing about the flexbox model is that you don’t have to remember — or even particularly understand — much of the math. While the Working Draft goes into detail on the calculation and distribution of free space, you can work safe in the knowledge that the browser will take care of this for you.
Animating Flexible Boxes
A simple and elegant effect is already at your fingertips. By making theli
elements in a navigation bar flexible, and specifying their width on :hover
, you can create a nice effect whereby the highlighted li
element expands and all the other elements shrink. Here’s the CSS for that:nav ul { display: box; width: 880px; } nav ul li { padding: 2px 5px; box-flex: 1; -webkit-transition: width 0.5s ease-out; min-width: 100px; } nav ul li:hover { width: 200px; }
You’ll spot a
min-width
on the li
element, which is used to fix a display bug in Chrome.Equal-Height Columns: The Happy Accident!
As we’ll see, all flexbox elements inherit a default value ofbox-align: stretch
. This means they will all stretch to fill their container.For example, two flexbox columns in a parent with
display: box
will always be the same height. This has been the subject of CSS and JavaScript hacks for years now.There are a number of practical implementations of this fortunate outcome, not the least of which is that sidebars can be made the same height as the main content. Now, a
border-left
on a right-hand sidebar will stretch the full length of the content. Happy days!box-orient and box-direction
Thebox-orient
property defines how boxes align within their parent. The default state is horizontal
or, more specifically, inline-axis
, which is horizontal and left-to-right in most Western cultures. Likewise, vertical
is the same as block-axis
. This will make sense if you think about how the browser lays out inline and block elements.You can change the
box-orient
value to vertical
to make boxes stack on top of each other. This is what we’ll do with the featured articles on our fruit blog.Here is what our articles look like with
box-orient
set to its default setting:Ouch! As you can see, the articles are stacking next to each other and so run off the side of the page. It also means that they sit on top of the sidebar. But by quickly setting the parent div to
box-orient: vertical
, the result is instant:A related property is
box-direction
, which specifies the direction in which the boxes are displayed. The default value is normal
, which means the boxes will display as they appear in the code. But if you change this value to reverse
, it will reverse the order, and so the last element in the code will appear first, and the first last.While
box-orient
and box-direction
are essential parts of the model, they will not likely appear in the final specification, because they are being merged into the flex-direction
property, which will take the following values: lr
, rl
, tb
, bt
, inline
, inline-reverse
, block
and block-reverse
. Most of these are self-explanatory, but as yet they don’t work in any browser.box-ordinal-group
Control over the order in which boxes are displayed does not stop atnormal
and reverse
. You can specify the exact order in which each box is placed.The value of
box-ordinal-group
is set as a positive integer. The lower the number (1
being the lowest), the higher the layout priority. So, an element with box-ordinal-group: 1
will be rendered before one with box-ordinal-group: 2
. If elements share the same box-ordinal-group
, then they will be rendered in the order that they appear in the HTML.Let’s apply this to a classic blog scenario: the sticky post (i.e. content that you want to keep at the top of the page). Now we can tag sticky posts with a
box-ordinal-group
value of 1
and all other posts with a box-ordinal-group
of 2
or lower. It might look something like this:article { box-ordinal-group: 2; } article.sticky { box-ordinal-group: 1; }So, any article with
class="sticky"
is moved to the top of the list, without the need for any front-end or back-end jiggering. That’s pretty impressive and incredibly useful.We’ve used this code in our example to stick a recent blog post to the top of the home page:
box-pack and box-align
Thebox-pack
and box-align
properties help us position boxes on the page.The default value for
box-align
is stretch
, and this is what we’ve been using implicitly so far. The stretch
value stretches the box to fit the container (together with any other siblings that are flexible boxes), and this is the behavior we’ve seen so far. But we can also set box-align
to center
and, depending on the box-orient
value, the element will be centered either vertically or horizontally.For example, if a parent inherits the default
box-align
value of horizontal
(inline-axis
), then any element with box-align
set to center
will be centered vertically.We can use this in our blog example to vertically center the search box in the header. Here’s the mark-up:
<header> <form id="search"> <label for="searchterm">Search</label> <input type="search" placeholder="What’s your favourite fruit…" name="searchterm" /> <button type="submit">Search!</button> </form> </header>And to vertically center the search box, we need just one line of CSS:
header { display: box; box-align: center; } header #search { display: box; box-flex: 1; }The height of
#search
has not been set and so depends on the element’s content. But no matter what the height of #search
, it will always be vertically centered within the header. No more CSS hacks for you!The other three properties of
box-align
are start
, end
and baseline
.When
box-orient
is set to horizontal
(inline-axis
), an element with box-align
set to start
will appear on the left, and one with box-align
set to end
will appear on the right. Likewise, when box-orient
is set to vertical
(block-axis
), an element with box-align
set to start
will appear at the top, and one with box-align
set to end
will move to the bottom. However, box-direction: reverse
will flip all of these rules on their head, so be warned!Finally, we have
baseline
, which is best explained by the specification:
Align all flexbox items so that their baselines line up, then distribute free space above and below the content. This only has an effect on flexbox items with a horizontal baseline in a horizontal flexbox, or flexbox items with a vertical baseline in a vertical flexbox. Otherwise, alignment for that flexbox item proceeds as if flex-align: auto
had been specified.
Another property helps us with alignment: box-pack
. This enables us to align elements on the axis that is perpendicular to the axis they are laid out on. So, as in the search-bar example, we have vertically aligned objects whose parent have box-orient
set to horizontal
.But what if we want to horizontally center a box that is already horizontally positioned? For this tricky task, we need
box-pack
.If you look at the navigation on our fruit blog, you’ll see that it’s only 880 pixels wide, and so it naturally starts at the left of the container.
We can reposition this
ul
by applying box-pack
to its parent. If we apply box-pack: center
to the navigation element, then the navigation moves nicely to the center of the container.This behaves much like
margin: 0 auto
. But with the margin trick, you must specify an explicit width for the element. Also, we can do more than just center the navigation with box-pack
. There are three other values: start
, end
and justify
. The start
and end
values do what they do for box-align
. But justify
is slightly different.The
justify
value acts the same as start
if there is only one element. But if there is more than one element, then it does the following:- It adds no additional space in front of the first element,
- It adds no additional space after the last element,
- It divides the remaining space between each element evenly.
box-flex-group and box-lines
The final two properties have limited and/or no support in browsers, but they are worth mentioning for the sake of thoroughness.Perhaps the least helpful is
box-flex-group
, which allows you to specify the priority in which boxes are resized. The lower the value (as a positive integer), the higher the priority. But I have yet to see an implementation of this that is either useful or functional. If you know different, please say so in the comments.On the other hand,
box-lines
is a bit more practical, if still a little experimental. By default, box-lines
is set to single
, which means that all of your boxes will be forced onto one row of the layout (or onto one column, depending on the box-orient
value). But if you change it to box-lines: multiple
whenever a box is wider or taller than its parent, then any subsequent boxes will be moved to a new row or column.Vendor Prefixes and Cross-Browser Support
It will come as no surprise to you that Internet Explorer does not (yet) support the flexbox model. Here’s how CanIUse sees the current browser landscape for flexbox:The good news is that Internet Explorer 10 is coming to the party. Download the platform preview, and then check out some interesting examples.
Also, we need to add a bunch of vendor prefixes to guarantee the widest possible support among other “modern” browsers. In a perfect world, we could rely on the following:
#parent { display: box; } #child { flex-box: 1; }But in the real world, we need to be more explicit:
#parent { display: -webkit-box; display: -moz-box; display: -o-box; display: box; } #child { -webkit-flex-box: 1; -moz-flex-box: 1; -o-flex-box: 1; flex-box: 1; }
Helper Classes
A shortcut to all of these vendor prefixes — and any page that relies on the flexbox model will have many of them — is to use helper classes. I’ve included them in the source code that accompanies this article. Here’s an example:.box { display: -webkit-box; display: -moz-box; display: -o-box; display: box; } .flex1 { -webkit-flex-box: 1; -moz-flex-box: 1; -o-flex-box: 1; flex-box: 1; } .flex2 { -webkit-flex-box: 2; -moz-flex-box: 2; -o-flex-box: 2; flex-box: 2; }This allows us to use this simple HTML:
<div class='box'> <div class='flex2' id="main"> <!-- Content here --> </div> <div class="flex1" id="side”> <!-- Content here --> </div> </div>Using non-semantic helper classes is considered bad practice by many; but with so many vendor prefixes, the shortcut can probably be forgiven. You might also consider using a “mixin” with Sass or Less, which will do the same job. This is something that Twitter sanctions in its preboot.less file.
Flexie.js
For those of you who want to start experimenting with flexbox now but are worried about IE support, a JavaScript polyfill is available to help you out.Flexie.js, by Richard Herrera, is a plug-and-play file that you simply need to include in your HTML (download it on GitHub). It will then search through your CSS files and make the necessary adjustments for IE — no small feat given that it is remapping much of the layout mark-up on the page.
A Word on Firefox
The flexbox model was, at least originally, based on a syntax that Mozilla used in its products. That syntax, called XUL, is a mark-up language designed for user interfaces.The irony here is that Firefox is still catching up, and its rendering of some flexbox properties can be buggy. Below are some issues to watch out for, which future releases of Firefox will fix. Credit here must go to the uber-smart Peter Gasston and Oli Studholme, giants on whose shoulders I stand.
- Flexbox ignores
overflow: hidden
and expands the flexbox child when the content is larger than the child’s width. - The setting
display: box
is treated asdisplay: inline-box
if there is no width. - The outline on flexbox children is padded as if by a transparent border of the same width.
- The setting
box-align: justify
does not work in Firefox. - If you set
box-flex
to0
, Firefox forces the element to act like it’s using the quirks-mode box model.
Summary
The flexbox model is another exciting development in the CSS3 specification, but the technology is still very much cutting-edge. With buggy support in Firefox and no support in Internet Explorer until version 10 moves beyond the platform preview, it is perhaps of limited use in the mainstream.Nevertheless, the spec is still a working document. So, by experimenting with these new techniques now, you can actively contribute to its development.
It’s hard to recommend the flexbox model for production websites, but envelopes need pushing, and it might well be the perfect way to lay out a new experimental website or idea that you’ve been working on.
Offering a range of new features that help us break free of the float, the flexbox model is another step forward for the layout of modern Web pages and applications. It will be interesting to see how the specification develops and what other delights for laying out pages await the Web design community in the near future.
No comments:
Post a Comment