Flexbox is the first CSS layout technique that works for the modern web. —Paddi MacDonnell
In Chapter 11, you saw that floats and inline blocks can get the job done, but not without running into problems, quirks, and workarounds such as clearing floats, creating faux columns, and avoiding whitespace. Even with all that, these layout strategies can't accomplish one of the features of the holy-grail layout: displaying the footer at the bottom of the screen if the page content doesn't fill the screen height.
This chapter's layout strategy prevents all these quirks, solves the footer problem, and has the fresh-faced appeal of a modern technology. I'm talking about flexbox, and before you can start using it for layout, you need to understand what it is and how it works. The next few sections explain everything you need to know.
Flexbox is the welcome shorthand for this method's cumbersome official moniker: Flexible Box Layout Module. The underlying principle behind flexbox is to provide a way around the rigid, cumbersome way that the browser handles blocks of content. The default is to stack them. Consider the following collection of div
elements:
<div class="container"> <div class="item itemA">A</div> <div class="item itemB">B</div> <div class="item itemC">C</div> <div class="item itemD">D</div> <div class="item itemE">E</div> <div class="item itemF">F</div> </div>
Not shown here are the classes I’ve applied to give each item element a unique background color, and Figure 12.1 shows the results. As you can see, the div
elements are stacked and extend the width of the browser window.
Flexbox rides to the rescue by offering simple but extremely powerful methods for laying out, distributing, aligning, sizing, and even ordering the child elements in a parent container. The flex part of the name comes from one of this technology's main tenets: The child items in a container should be able to change dimensions (width and height) by growing to fill in empty space if there's too much of it or by shrinking to allow for a reduction in space. This happens whether the amount of content changes or the size of the screen changes (such as by maximizing a window or by changing a device's screen orientation).
So flexbox is perfect, then? No, it's not. It has two main drawbacks:
Learn
To learn CSS Grid basics now, see my tutorial “Getting Started with CSS Grid” on the Web Design Playground. Online:wdpg.io/grid
When you work with flexbox, you work with two kinds of page objects: containers and items. A flex container is any type of parent block element—div
, p
, any of the HTML semantic page elements you learned in Chapter 11, even the body
element—that surrounds one or more elements. These child elements are called flex items.
Okay, that’s enough theory. It’s time to start learning how flexbox works.
Before you can do anything with flexbox, you need to decide which block-level element will be the flex container. When you've done that, you convert that element to a container with a single CSS declaration: display: flex
. The following rule turns the header
element into a flex container:
header { display: flex; }
I find that the best way to learn about and use flexbox is to ask yourself a series of questions—one set for containers and another for items. Here are the container questions:
(Don't worry if you're not sure what I mean by main axis and cross axis. All will be revealed in the next section.) The next few sections ask and show you the possible answers to each of these questions.
The first thing that's flexible about flexbox is that it doesn't dictate one and only one direction for the container's items. Although the browser's default layout rigidly enforces a vertical direction, and although floats and inline blocks work only horizontally, flexbox is happy to go either way. With flexbox, you decide.
Perhaps the most important flexbox concept to grasp right from the get-go is the notion that flexbox containers always have two axes:
You determine the main-axis direction when you set the flex-direction
property on a container:
container
{
display: flex;
flex-direction: row|row-reverse|column|column-reverse;
}
row
—Sets the main axis to horizontal, with items running from left to right (the default)row-reverse
—Sets the main axis to horizontal, with items running from right to leftcolumn
—Sets the main axis to vertical, with items running from top to bottomcolumn-reverse
—Sets the main axis to vertical, with items running from bottom to top.container
{
display: flex;
flex-direction: row;
}
Remember
The row
value is the default, so declaring flex-direction: row
is optional.
Play
You can try out all the flex-direction
values interactively on the Playground. Online:wdpg.io/12-1-2
Remember
If you applied flex-direction: column
to this example, you'd get the layout shown in Figure 12.1 earlier in this chapter; the main axis would run from top to bottom, and the cross axis would run left to right. If you applied flex-direction: column-reverse
, you'd get the same layout with the div
elements in reverse order; the main axis would run bottom to top, and the cross axis would remain left to right.
Play
How would you use flexbox to display a numbered list in reverse order? Online:wdpg.io/12-1-4
When you've used flex-direction
to set the main axis for the container, your next decision is how you want the items to be arranged along that axis. Use the justify-content
property on a container:
container
{
display: flex;
justify-content: flex-start|flex-end|center|space-between|space-around;
}
flex-start
—Places the items at the beginning of the container (the default)flex-end
—Places the items at the end of the containercenter
—Places the items in the middle of the containerspace-between
—Places the items with the first item at the beginning of the container, the last item at the end, and the rest of the items evenly distributed in betweenspace-around
—Distributes the items evenly within the container by supplying each item the same amount of space on either sideBeware
The space-around
value doesn't quite work as advertised because you always get less space before the first item and after the last item (see Figure 12.5 later in this chapter). That happens because each item is given a set amount of space on either side, so inside items have two units of space between them, compared with one unit of space before the first item and after the last item.
Remember
The flex-start
value is the default, so declaring justify-content: flex-start
is optional.
Play
You can play around with the justify-content
values interactively on the Playground. Online:wdpg.io/12-1-5
With the items arranged along the main axis, your next task is choosing an arrangement along the cross axis. You set this by using the container's align-items
property:
container
{
display: flex;
align-items: stretch|flex-start|flex-end|center|baseline;
}
stretch
—Expands each item along the cross axis to fill the container (the default)flex-start
—Aligns the items with the beginning of the cross axisflex-end
—Aligns the items at the end of the cross axiscenter
—Aligns the items in the middle of the cross axisbaseline
—Aligns the items along their baseline of the flex containerRemember
The stretch
value is the default, so declaring align-items: stretch
is optional.
Play
You can try out the different align-items
values interactively on the Playground. Online:wdpg.io/12-1-6
FAQ
Are these alignment options confusing, or is it just me? Almost everyone getting started with flexbox finds alignment to be the most confusing part. It may help to think of the main axis as the justification axis, because you use the justify-content
property to arrange items on that axis. Similarly, think of the cross axis as the alignment axis, because you arrange items on it using the align-items
property.
By default, flexbox treats a container as a single row (if you've declared flex-direction
as row
or row-reverse
) or as a single column (if you've declared flex-direction
as column
or column-reverse
). If the container's items are too big to fit into the row or column, flexbox shrinks the items along the main axis to make them fit. Alternatively, you can force the browser to wrap the container's items to multiple rows or columns rather than shrinking them. You do this by using the container's flex-wrap
property:
container
{
display: flex;
flex-wrap: nowrap|wrap|wrap-reverse;
}
nowrap
—Doesn't wrap the container's items (the default)wrap
—Wraps the items to as many rows or columns as neededwrap-reverse
—Wraps the items at the end of the cross axisRemember
Play
You can wrap your head around the three flex-wrap
values by trying them out interactively on the Playground. Online:wdpg.io/12-1-8
Your final container-related decision is how you want multiple lines—that is, multiple rows or columns—arranged along the cross axis. This is similar to arranging individual flex items along the main axis, except that here, you're dealing with entire lines of items. You control this arrangement by using the container's align-content
property:
container
{
display: flex;
align-content: stretch|center|flex-start|flex-end|space-between|space-around;
}
stretch
—Expands the wrapped lines along the cross axis to fill the container height (the default)center
—Places the lines in the middle of the cross axisflex-start
—Places the lines at the beginning of the cross axisflex-end
—Places the lines at the end of the cross axisspace-between
—Places the first line at the beginning of the cross axis, the last line at the end, and the rest of the lines evenly distributed in betweenspace-around
—Distributes the lines evenly within the container by supplying each line with a set amount of space on either sideBeware
As with justify-content
, the space-around
value gives one unit of space before the first line and after the last line but two units of space between all the other lines.
Remember
The stretch
value is the default, so declaring align-content: stretch
is optional.
Play
You can try out all the align-content
values interactively on the Playground. Online:wdpg.io/12-1/10
Covers:flex
and other flex container properties
Online: wdpg.io/12-1-1
By far the most common question related to web page layouts is a deceptively simple one: How do I center an element horizontally and vertically? That is, how can you use CSS to place an element in the dead center of the browser window? Over the years, many clever tricks have been created to achieve this goal, with most of them using advanced and complex CSS rules. Fortunately, you don't have to worry about any of that because flexbox lets you dead-center any element with four lines of CSS, as shown in the following example.
Online:wdpg.io/12-1-1
This example shows you how to center an h1
element horizontally and vertically within the browser window.
div { display: flex; justify-content: center; ① align-items: center; ② height: 100vh; ③ }
① Center the h1
horizontally.
② Center the h1
vertically.
③ Set the div
to the height of the window.
<div> <h1>Center Me!</h1> </div>
This example works by turning the div
element into a flex container, which automatically converts the h1
element to a flex item. By setting both justify-content
and align-items
to center
, and by giving the div
the full height of the browser window (it's the width of the browser window by default), you center the h1
in the window.
Now that you know everything that's worth knowing about flexbox containers, turn your attention to the flexbox items inside those containers. As before, learning about and using flex items is best approached by asking yourself a series of questions:
If you look back at Figure 12.4, notice that in the flex-start
example, the flex items are bunched up at the beginning of the container, leaving a chunk of empty space to the right. This effect may be what you want, or you may prefer to have the items fill that empty space. You can do that by applying the flex-grow
property to the item you want to expand:
item
{ flex-grow:value
; }
By default, all flex items are given a flex-grow
value of 0
. To grow items to fill a container's empty space, you assign positive numbers to those items as follows (see Figure 12.8):
1,
2
, and 1
to three items, those items get 25 percent, 50 percent, and 25 percent of the empty space, respectively.Master
To calculate what proportion of the empty space is assigned to each item, add all the flex-grow
values for a given container and then divide the individual flex-grow
values by that total. Values of 1
, 2
, and 1
add up to 4
, for example, so the percentages are 25 (¼), 50 (2/4), and 25 (¼), respectively.
Play
You can play with various flex-grow
values interactively on the Playground. Online:wdpg.io/12-2-2
The opposite problem of expanding flex items to fill a container's empty space is shrinking flex items when the container doesn't have enough space. This shrinking is activated by default, so if the browser detects that the flex items are too large to fit the container, it automatically reduces the flex items to fit.
How much each item shrinks depends on its size in relation to the other items and the size of the container. Suppose that you’re working with a horizontal main axis (that is, flex-direction
is set to row
) and that the container is 1200px wide, but each of its five items is 400px wide. That's 2000px total, so the browser must reduce the items by 800px to fit the container. In this case, because all the items are the same width, the browser reduces the width of each by 160px.
If the items have different widths, the calculations get more complicated, so I won't go into them here. Suffice it to say that the amount each item's width gets reduced depends on its initial width. The greater the initial width is, the more the item shrinks.
Learn
Mike Reithmuller has a lucid explanation of the math involved in calculating item shrinkage here: https://madebymike.com.au/writing/understanding-flexbox.
Rather than let the browser determine how much each item gets reduced, you can specify that a particular item be reduced more than or less than the other items. You do that by applying the flex-shrink
property to the item:
item
{ flex-shrink:value
; }
By default, all flex items are given a flex-shrink
value of 1
, which means that they're all treated equally when it comes time to calculate the shrink factor. To control the shrink factor yourself, assign positive values to those items as follows (see Figure 12.9):
flex-shrink
to a number greater than 1
, the browser shrinks the item more than the other items by a factor that's somewhat proportional to the value you provide. (Again, the math is quite complicated.)flex-shrink
to a number greater than 0
but less than 1
, the browser shrinks the item less than the other items.flex-shrink
to 0
, the browser doesn’t shrink the item.Beware
The browser won't shrink an item to a size less than the minimum required to display its content. If you keep increasing an item's flex-shrink
value, and the item refuses to get smaller, the item is probably at its minimum possible size.
Play
You can try out various flex-shrink
values interactively on the Playground. Online:wdpg.io/12-2-5
You've seen that flex items can grow or shrink depending on how they fit in the container and that you have some control of this process via the flex-grow
and flex-shrink
properties. But when I say that flex items can grow or shrink, what are they growing and shrinking from? That depends:
width
value (if flex-direction
is set to row
) or a declared height
value (if flex-direction
is set to column
), the item grows or shrinks from this initial size.The latter case—that is, not having a declared width
(for flex-direction: row
) or height
(for flex-direction: column
)—causes two problems. First, it prevents an item from shrinking smaller than its content. Second, the initial size (that is, the minimum required to display the content) may be smaller than you require. You can solve both problems by declaring a flex basis, which is a suggested size for the item. You do that by applying the flex-basis
property:
item
{ flex-basis:value
|auto|content; }
value
—Sets a specific measure for the width (with flex-direction: row
) or height (with flex-direction: column
). You can use any of the CSS measurement units you learned about in Chapter 7, including px
, em
, rem
, vw
, and vh
. You can also set value
to a percentage.auto
—Lets the browser set the initial value based on the item's width
or height
property (the default). In the absence of a declared width or height, auto
is the same as content
, discussed next.content
—Sets the initial width or height based on the content of the item.You should know that flexbox offers a shorthand property for flex-grow
, flex-shrink
, and flex-basis
. This property is named flex
, and it uses any of the following syntaxes:
item
{ flex:flex-grow
flex-shrink
flex-basis
; flex:flex-grow
flex-shrink
; flex:flex-grow
flex-basis
; flex:flex-grow
; flex:flex-basis
; }
Here's an example declaration that uses the default values for each property:
flex: 0 1 auto;
This example sets flex-grow
to 1 and flex-shrink
to 0:
flex: 1 0;
This final example styles an item with a fixed size of 10em:
flex: 0 0 10em;
One of the most surprising—and surprisingly handy—tricks offered by flexbox is the ability to change the order of the items in a container. When would you use this feature? Here are two common scenarios:
item
{ order:value
; }
By default, all the items in a flex container are given an order
value of 0
. You can manipulate the item order as follows:
order
value, the later it appears in the container.order
value appears last in the container.order
value appears first in the container.Master
Negative order
values are allowed, so an easy way to move an item to the front of its container is to set its order
value to -1.
Play
You can mess around with some order
values interactively on the Playground. Online:wdpg.io/12-2-6
You saw earlier that you can use the align-items
property to arrange items along a container's cross axis. Rather than align all the items the same way, you may prefer to override this global alignment and assign a different alignment to an item. You can do that by setting the item's align-self
property:
item
{
align-self: stretch|flex-start|flex-end|center|baseline;
}
The good news about flexbox browser support is that it works in all current browsers. In fact, it works even in the vast majority of recent browsers, so for the most part, you don’t have to worry about using browser prefixes.
If you have to support old browsers, however, some prefixing is required to get flexbox to work. These prefixes can get complex because the flexbox syntax changed between versions, so supporting older browsers means supporting these older syntaxes. Rather than run through all these prefixes, I'm going to pass the buck to a fantastic tool called Autoprefixer (http://autoprefixer.github.io), shown in Figure 12.12. You paste your nonprefixed code into the left pane, and fully prefixed code appears automagically in the right pane. It also comes with a Filter box that you can use to specify how far back you want to go with browser support:
last
x
versions
to support that most recent x
versions of all browsers (such as last 4 versions
).>
y
%
to support only web browsers that have at least y
%
market share (such as > .5%
).Covers: The flex
-grow
and flex-shrink
properties
Online: wdpg.io/12-2-0
A common web page component is a simple thumbnail list that has a thumbnail image on the left and a description or other information on the right. These elements are used for photo galleries, user directories, book lists, project summaries, and much more. Getting the image and the text to behave is tricky with garden-variety CSS, but it's a breeze with flexbox, as shown in the following example.
Online:wdpg.io/12-2-1
This example shows you how to use flexbox to create a thumbnail list of items.
.dictionary-container { list-style-type: none; } .dictionary-item { display: flex; ① } .dictionary-image { flex-shrink: 0; ② } .dictionary-entry { flex-grow: 1; ③ }
① Each li
becomes a flex container.
② Prevent the thumbnail from shrinking.
③ Allow the text to use the rest of the container.
<ul class="dictionary-container"> <li class="dictionary-item"> <div class="dictionary-image"> <img src="/images/animalpath.jpg" alt="Photo of an animal path"> </div> <div class="dictionary-entry"> <h4>animal path</h4> <p>A footpath or track made by the constant and long-term walking of animals.</p> </div> </li> etc. </ul>
Covers: Layout with flex
and other flexbox properties
Online: wdpg.io/12-3-0
Okay, now you can turn your attention to building the holy-grail layout with flexbox. As before, the holy grail includes three instances in which you need content side by side: the header, the navigation bar, and the content columns. In all three instances, you’ll place the elements in a flexbox container with a horizontal main axis.
First, however, note that you want these elements stacked, which means that they need a flex container that uses a vertical main axis. The <body>
tag does the job nicely, so set body
as a flex container with a vertical main axis and the content starting at the top:
body { display: flex; flex-direction: column; justify-content: flex-start; max-width: 50em; min-height: 100vh; }
Note, too, that I specified a maximum width for the container and a minimum height. You'll see why I used 100vh when I talk about adding a footer a bit later.
Now do the header, as shown in the following example.
Online:wdpg.io/12-3-1
This example shows you how to use flexbox to get the header logo and title side by side.
header { display: flex; ① justify-content: flex-start; ① align-items: center; ① border: 1px solid black; padding: 1em; } header img { flex-shrink: 0; ② } h1 { flex-grow: 1; ③ padding-left: .5em; font-size: 2.5em; }
① Display the header
element as a flex container.
② Prevent the logo from shrinking.
③ Let the h1
element use the rest of the header space.
<header> <img src="/images/your-logo-here.tif" alt="Our logo"> <h1>Site Title</h1> </header>
In this code, I converted the header element to a flex container with the items arranged at the start of the main (horizontal) axis and centered on the cross (vertical) axis.
Now convert the navigation bar to a horizontal flex container, as shown in the following example.
Online:wdpg.io/12-3-2
This example shows you how to use flexbox to get the navigation-bar items side by side.
nav { padding: .5em; border: 1px solid black; } nav ul { display: flex; ① justify-content: flex-start; ① list-style-type: none; padding-left: .5em; } nav li { padding-right: 1.5em; }
① Display the ul
element as a flex container.
<nav> <ul> <li>Home</li> <li>Item</li> <li>Item</li> <li>Item</li> </ul> </nav>
In this case, the ul
element is converted to a flex container, meaning that the li
elements become flex items arranged horizontally from the start of the container.
Next, convert the main
element's <article>
and <aside>
tags to flex items, which gives you the two-column content layout. The following example shows how it's done.
Online:wdpg.io/12-3-3
This example shows you how to use flexbox to get the article
and aside
elements side by side in a two-column layout.
main { display: flex; ① flex-grow: 1; ② } article { flex-grow: 3; ③
border: 1px solid black;
}
aside {
flex-grow: 1; ④
border: 1px solid black;
}
① Display main
as a flex container.
② Let it grow vertically.
③ Let article
use three units of space
④ Let aside
use one unit of space.
<main> <article> <h2>Article Title</h2> <p>Article paragraph 1</p> <p>Article paragraph 2</p> </article> <aside> <h3>Sidebar Title</h3> <p>Sidebar paragraph</p> </aside> </main>
A couple of interesting things are going on here. First, note that the main
element does double duty: It acts as the flex container for the article
and aside
elements, and it's a flex item in the body
element's flex container. Setting flex-grow
to 1
for the main
element tells the browser to give main
all the empty vertical space in the body
container. Again, why you’re doing this will become apparent when you get to the footer.
For the article
and aside
flex items, I assigned flex-grow
values of 3
and 1
, respectively, meaning that article
gets 75 percent of the available horizontal space and aside
gets the remaining 25 percent.
Master
Note, too, that the article
and aside
items are the same height—a pleasant bonus that comes courtesy of the body
container's default stretch
value for align-items
. You get a true full-height sidebar and don't have to resort to a faux column.
Finally, add the footer
element in the same way that you did with the float and inline block layouts in Chapter 11. Figure 12.13 shows the result.
Can you see what's different? That's right: The footer
element appears at the bottom of the browser window, which is where it should be in a true holy-grail layout. You got that nice touch by doing three things:
body
element into a flex container with a vertical main axismin-height: 100vh
on the body
element, which forces the body
element to always be at least the same height as the browser windowflex-grow: 1
on the main
element to force it to use any available empty vertical space in the body
containerPlay
How would you modify this layout to display the sidebar on the left instead of the right? Online:wdpg.io/12-3-5
Play
How would you modify this layout to display three content columns: a sidebar to the left and to the right of the article
element? Online:wdpg.io/12-3-6
flex-direction
.justify-content
.align-items
property.flex-wrap
.align-content
property.flex-grow
.flex-shrink
.flex-basis
property.order
property.align-self
.