Flexbox adventures

- 8 min read

One of the Flexbox’s greatest strengths is its ability to calculate space, this is a huge gain when it comes to lists of items where space is an issue but we can’t determine how many items are going to exist. To be more specific, navigation and grids are probably the places where Flexbox will be used for the biggest gains over our current methods. Imagine the case of a CMS where a user might be adding or removing navigation items and we do not want to alter the stylesheet but do want to fill the space. You can even have it calculate different types of units together.

For those who are unfamiliar with what Flexbox actually is, it's a smart layout mode in CSS that calculates and distributes space, which will solve a number of layout problems and hacks that we've wrestled with for years.

Flexbox basics

Change can be difficult, much like moving from table based layouts to floats, Flexbox can cause some major confusion at first.

The first basic concept is to understand Flexbox is a parent and child relationship, while only the parent technically needs display:flex for it to control the layout of its children, usually I'll put it on the child element to control its contents.

The second is how the shorthand works, if you've looked at Flexbox before you may have seen something like Flex:1 0 auto. This shorthand is broken down into three flexbox properties in this order: flex-grow, flex-shrink, and flex-basis.

Flex-basis and Flex-grow

Flex-basis determines how the other two properties behave, as its name suggests flex-basis is the basis on which the flexbox item knows how much it can grow or shrink to fill missing space. It's the initial size of each flex item, and can be restricted to be the only amount by specifying 0 on grow and shrink.

  • 2 / 0 / 100px;
  • 0 / 0 / 150px;
  • 2 / 0 / 100px;

Applying Grow

In the above example, the middle item has not been given permission to grow or shrink so it stays at exactly 150px, the remaining space for the items to flex into is 450px, they've both been given an equal share of 2 and their basis is the same, so 225px is distributed to both items evenly.

It's important to note that flex-grow is not relative to each other item. An Item with flex-grow:4 won't necessarily be twice the size of an item with flex-grow:2, all it's actually doing is saying it will get twice the available space of the other item.

Another example of how flex-grow works, if you were to adjust the flex-basis on the third item to be 50px you'll see a different result as shown below:

  • 2 / 0 / 100px;
  • 0 / 0 / 150px;
  • 2 / 0 / 50px;

Calculating the space

As we see there are two different sizes, and as I stated before it does not mean that Item 1 and Item 3 are equal size, that's because of how the distribution of space takes into account the flex-basis.

I've attempted to create a formula to help work out how much actual pixels are being given to each item. The first calculation is fairly simple:

Available space = (container size - flex-basis siblings total)

Available space is what flexbox uses to distribute its grow amounts, let's create a formula for working out how much each grow amount will be. Our available space in this example is: container size(600) - basis total (300) = 300.

Grow unit = (Available space / Sum Grow siblings total).

Our grow amount can be figured out first by adding all of our sibling grow numbers together ( 2 + 0 + 2 = 4). Available space 300 / 4 = 75. A Grow unit is 75px in size.

Flex item size = (Flex basis + (Grow Unit * num))

So with this final calculation item 1 (flex: 2 0 100px) is equal to 100px + (75*2) which is 250px. Item 2 (flex: 0 0 150px) is equal to 150px, and Item 3 (flex: 2 0 50px) is equal to 50px + (75*2) which is 200px. We can simplify the formula a bit:

Available space = (Container size - Flex-basis siblings total). Flex Item size = Basis + ((Available space / Total Grow nums)* Individual grow num).

Double checking

In the below example I've added 4 items and a larger container to further test, lets try the calculation again.

  • 1 / 0 / 150px;
  • 3 / 0 / 200px;
  • 2 / 0 / 175px;
  • 4 / 0 / 225px;

Assuming this is being viewed on a larger screen: Container (1000) - Basis Total(150+200+175+225) = Available space(250).

Available space(250) / Sum grow siblings (1 + 3 + 2 + 4) = Grow unit size (25). Final calculated flex items below.

  1. 150 + (25 * 1) = 175
  2. 200 + (25 * 3) = 275
  3. 175 + (25 * 2) = 225
  4. 225 + (25 * 4) = 325

A rounding bug

I've noticed that in some cases Firefox 32 will round a figure in a bizarre way, I've had firefox calculate my flex to 1 pixel over the amount it should be allowed, whereas other browsers stuck to the maximum of the container size. This might also be related to a bug I've seen in Firefox where it looks like part of a flex item is seperated from its next flex item by what looks like half a pixel.

Update: I've received some confirmation, that the 'bug' is actually Firefox devtools misreporting the width of the item, whereas the computed style is actually correct.

Flex Shrink

Flex-shrink, also known as the second number in the short hand flex:1 1 10px, is applied when there is an overflow in the calculation. It will then squash the item where it needs to.

  • Flex shrink: 1,
    Flex basis: 200px
  • Flex Shrink: 2,
    Flex basis: 200px
  • Flex Shrink: 3,
    Flex basis: 200px
  • Flex Shrink 1,
    Flex basis: 200px

Originally, I thought I had a formula for calculating how flex-shrink is applied:

Flex Item shrunk = Basis - (Shrink num / Total shrink nums * overflow space).

After numerous tests, I kept coming across edge cases where it wasn't applying this way and decided that flex-shrink is in fact sorcery. Even after reading the spec, in particular this part, and checking against the way in which it was calculated on the Flexbox basics article on the Opera blog.

Above is an example of flex-shrink not applying how I would expect it. It seems to be common amongst how the browsers are applying shrink, which implies that it's intended, but I just can't seem to work out the pattern. Some examples of where it defies my calculation are: this flexbox shrink test and this flex-shrink test.

The actual calculation

After I lost actual sleep trying to get my head around it, my friend Mike Riethmuller finally helped me figure it out, now I can sleep easier. Let's say we have a container maximum width of 600px and a negative available space (-200px) like in the previous example, first multiply each basis by its shrink value and add together.

Item 1 (1x 200) + Item 2 (2 x 200) + Item 3 (3 x 200) + Item 4 (1 x 200) = Total items (1400)

Then we divide each item by the total items. Item 2 for example:

Item 2 shrink factor = Item 2 (2 x 200px) / Total Items (1400) = 0.286

Then we multiply that by the negative space (-200px).

Space removed from Item 2 = Item 2 shrink factor (0.286) x Negative space (-200px) = -57.142. Rounded down to 57.

Relative distributions

The only way to make sure that each item is a perfect multiple of another item is to set your flex-basis to 0.

  • 3 / 0 / 0;
  • 1 / 0 / 0;
  • 2 / 0 / 0;

In this example, because each item has a basis of 0, the items have a relative relationship with each other, 3 will be 3 times the size of 1 and 1.5 times the size of 2. If you do this it will not wrap (because you're not giving it the necessary limitations to wrap).

Source ordering

Flex also gives us a way to deal with source ordering, an object which is positioned by flexbox can have its positioned swapped with another object by changing the order property, this could potentially be problematic for keyboard users if you're using it all over the place - but the option is welcome and at least a starting point for better tailoring content for different device sizes.

You might wonder when you'd actually want to use ordering. Source ordering could be useful for helping with content management systems deal with visual 'pinned' items. Imagine your list of news stories is laid out and ordered by date, you don't want to change the structure but perhaps you want to feature an item that isn't necessarily the newest item - ordering it could be a way of solving that problem without affecting the key functionality:

An order of -1 will ensure that the item is always the first item in the list. Below is how this works (and the actual list CSS).

						

	/* Every item in the grid */
	.d-flex-order > li {

		display:flex;
		/* be 33% but don't be flexible */
		flex:0 0 33%;
		list-style:none;

	}
	/* Featured item */
	.d-flex-order > li.d-flex-pinned {

		/* always put this item first in the visual order */
		order:-1;

		/* double the size of this in our grid */
		flex:0 0 66%;
	}

	/* The parent element */
	.d-flex-order {

		/* makes all items on a row equal height */
		align-items:stretch;

		display:flex;

		/* wrap onto the next line rather than stretching or shrinking */
		flex-wrap:wrap;

		/* tells flexbox to start items from the left */
		justify-content:flex-start;

		max-width:960px;
		margin:0 auto;

	}

						
					

Reverse it

Likewise, you might wonder when you'd ever use row-reverse or column reverse - it messes with the keyboard tab order. If you view reverses as an enhancement to an existing activity, it helps uncover the answer: sorting mechanisms. Search item results, image results, image galleries. It gives us a very simple way to reverse the order of these items by toggling one css property. If you don't have access to it it won't break the functionality. If you do, it's a plus.

Vertical center & equal height columns

Flexbox also, finally, gives us a less-hacky way to vertically center content with CSS, a detail developers have wrestled with since tables were replaced with floats. As you may have noticed in one of the previous examples, we can also stretch items to fill out the space they're in that matches the siblings in their row or column.

Vertical center div

Vertical centering is incredibly easy and requires just a few properties. Much easier than alternative hacks.

CSS Vertical centering is a sign of the apocalypse.

The only thing required is that both items are flex, and that the parent container has align-items:center.

						

	/* Vertical align */

	.d-flex-vertical {

		/* Tell flexbox to start vertically from the center */
		align-items:center;

		display:flex;

		/* I'm also aligning it horizontally */
		justify-content:center;

	}
	/* all the child element needs is to be a flex item */
	.d-flex-vertical > div {

		display:flex;

	}

						
					

Equal Height columns

We've had all kinds of hacky ways of dealing with equal height columns in the past, now we have a nice simple way.

Note: If you are viewing this on a small screen, you won't see equal height columns in this example, you'll see the items wrap instead. That's not to say it can't be done, but beyond the scope of this example.

Column one

You'll notice that even though this column has far more content in it, instead of the other columns ending early, they size themselves to meet the end of this column vertically.

Column two

This is a column with not much content.

Column Three

Normally, the only way to achieve this would be either a hack, or to set all boxes to min-height.

Another fairly simple piece of code is required to achieve this, align-items on the parent, and both items need display:flex

						

	/* Equal height columns */
	.d-flex-parent {

		display:flex;
		align-items:stretch;

	}
	.d-flex-child {

		display:flex;
	}

						
					

Flexbox transitions

You can animate the change almost any property of flexbox, bar a few, and have them transition. Since animating layout properties has never been a big priority of mine, most of these animations feel to be a little redundant.

The one thing I would love to animate is inserts and removes. A transition is not enough to detect the addition or removal of an item, so adding a transition and then inserting will only give you a jarring jolt as the action takes place.

To make inserts and removes less jarring you need to make use of transitionStart/transitionEnd or animationStart/animationEnd javascript events to be able make the action smoother.

Flexbox feature support

While organisations are still catching up with this decade’s internet browsers and operating systems, support for flex box is fairly shaky.

Even with more modern browsers - some mobile devices such as Windows Phone and Android 4.x are still going to require some old syntax, and while helpful tools like grunt-autoprefixer will feel like magic, my personal experience with them has shown them not to be so perfect.

To elaborate on this, when I laid out my Experiments list page with Flexbox and was testing on a Galaxy S3 using Android 4.1.2, I found I had to actually manually add -webkit-box-orient:vertical, on top of the newer syntax to get it close to working properly. In hindsight, it's probably my grunt config file being incorrectly configured, or how I've written the new syntax for it to interpret the old. I can't say yet. Either way, it wasn't a desired result and will require further investigation.

While I'd definitely recommend using one of these excellent tools because they will make your life easier, at the time of writing this I didn't find them reliable, whether the problem was with my use of the tools or the tools themselves. Make sure you test often with older devices to avoid heartache when dealing with the minefield of patchy syntax support.

						

	/*
		Here are some examples of differences between old and new.
		This is why we can't have nice things.
		But also, autoprefixer saves headaches.
	*/

	.display {

		/* Current */
		display:flex;
		display:inline-flex;

		/* older */
		display:-webkit-flex;
		display:-ms-flexbox;
		display:-webkit-box;
	}
	.axes {

		/* Current */
		justify-content: flex-start;

		align-items: stretch;


		/* older */
		-ms-flex-pack: start;
		-webkit-justify-content:flex-start;
		-webkit-box-pack:start

		-webkit-box-align:stretch;
		-webkit-align-items:stretch;
		-ms-flex-align:stretch;


	}
	.directions {

		/* Current */
		order: 1;
		flex-wrap: wrap;
		flex-direction: column;
		/* older */

		-webkit-box-wrap:wrap;
		-webkit-flex-wrap:wrap;
		-ms-flex-wrap:wrap;

		-ms-flex-direction:column;
		-webkit-box-orient:vertical;
		-webkit-box-direction: normal;

	}
	.flexin {
		/* current */
		flex:1 0 30%;

		/* older */
		-webkit-box-flex:1;
		-webkit-flex:1 0 30%;
		-ms-flex:1 0 30%;

	}


						
					

Enhancing to flexbox

Because Flexbox does so many things there are different scenarios where the base that you'd enhance from simply doesn't exist, it's a battle between progressive enhancement and graceful degradation.

						

	/*
		An example of this approach
		Enhancing navigation using Modernizr for feature detection
		to tell us if we should add flexbox
	*/

	/* Base */

	.d-navigation {

		text-align:center;

	}
	.d-navigation li {

		display:inline-block;

	}

	/* Enhanced */

	.flexbox .d-navigation {

		display:flex;
		justify-content: space-between;

	}
	.flexbox .d-navigation li {

		display:flex;

	}




						
					

Wrapping up

Flexbox has been with us in a few different forms since around 2009, yet the browser and device support we have is sporadic - I'd recommend using Flexbox as a way to enhance small components for the for the time being as a way to manage incremental improvements on user interface components.

The support isn't entirely there for portable devices so it's worth using an auto prefixer, and then double-checking if the auto prefixer is applying the necessary properties to the older devices.

Even though it seems like people won't stop using the old technology for years, doesn't mean we can't start using the new technology today: with feature detection, and progressive enhancement we don't have to be stuck in 2009, we're liberated from browsers, we can learn and try new technologies like Flexbox without the fear of whatever old version of IE or Android people are using as long as we provide them a baseline experience. Where flexbox gets tricky is with dealing with older syntaxes, so it's advisable to play it safe with smaller components rather than larger scale grid systems at this point.