CSS (Step 2: Flexbox)

With the basics and terminology out of the way, we come to layout. I barely touched it last time—all we had was some articles and boring old vertical scroll. Yeah, we did some fancy things, but nothing groundbreaking, or even that interesting.

Today, we’re going to tackle flexbox. Like all of these, this isn’t going to be a comprehensive tutorial, but if you’re reading this, it’s probably at least as new to you as it is to me.

First off, you might be happy to know that flexbox can make vertical centering not only possible, but simple! And it’s not the only solution on that front, but we’ll get there eventually.

You want columns? Easy peasy! You want them to resize proportionally depending on the viewport? No problem! You want them to stay the same size regardless? Still simple! Reorder those columns depending on viewport or media or because it’s Tuesday? Done. You want a different number of columns depending on viewport? Do you want to make sure the space between the columns is even regardless of the column’s size, or make sure the center of the columns are evenly spaced, or there’s an even amount of space between the columns? Do you want to vertically stretch columns so you don’t have a ragged bottom? Would you prefer them all with the same centerline? How about a ragged top and aligned at the bottom? Swap out columns and rows in the above and you’re still set, all with one new tool in your box!

This probably sounds too good to be true, but I promise, it works. It’s not the be-all, end-all for layout, and this won’t cover some designs, but it’s pretty…flexible (I think when writing about flexbox that joke is compulsory)! And you might wonder about browser support, or be worried about a zillion polyfills cluttering your code. Fret not! All real browsers support it, and have for years. IE 11 technically supports it, but it’s buggy.

This time we’re going to change things up. Instead of a blog-like page, let’s build a user testimonials page for a business, Spacely’s Space Sprockets. How, exactly, are they better than Cogswell’s Cogs?

We have to put our testimonial blocks in a container, first, and set that container to display:flex. But if we leave it like that, all the nested divs end up in one horizontal line. Sometimes that’s appropriate, but not typically, and not here.

Now we get on to the magic.

flex-direction tells the browser how to order the boxes. Still in the “container” class of the CSS, play around with the following:

flex-direction:row;
flex-direction:row-reverse;
flex-direction:column;
flex-direction:column-reverse;

row will have no (additional) effect, because that’s the default once you have the container set to display:flex. row-reverse might confuse you a bit—it does reverse the order of the contained images, but it does this by stacking them from right to left in your viewport. Your browser may not recognize that you may need to scroll left to see some of the nested elements. column will be what you saw before adding display:flex, and column-reverse will be a similar column, but with the opposite order.

What we really want is to wrap these testimonial blocks. flex-wrap to the rescue!

flex-wrap:wrap;
flex-wrap:nowrap;
flex-wrap:wrap-reverse;

If you add flex-wrap:wrap, you expect that the boxes will stack up nicely next to each other–but they don’t! The divs appear to have been resized horizontally, but are still stacked in a column. flex-direction won’t help here, because they are, technically, in a row, but wrapped as if each box was a word in a paragraph of text (which by default automatically wraps). If you have a wide enough viewport, or you zoom out, you may be able to get them to line up side-by-side, but we need a solution for this, because instead of fixing the problem, we created a new one!

Before we move on, you can combine the above attributes with the shorthand property flex-flow, e.g. flex-flow row-reverse wrap;.

We can ameliorate the issue by defining a specific width to the item, and that’s what we’ll do for now. Since I chose 300px wide images, let’s set the item (.testimonial) width to 300px.

Take note of how wrap-reverse and row-reverse/column-reverse differ. With wrap-reverse, you keep stacking images from right to left but, in the case of a row, when you get to the end (left), you shift the entire row down and begin a new row on top (instead of moving your stacking down a row.

Notice, since I have 11 testimonials, regardless of your viewport (okay, so long as the boxes line up side-by-side and it actually does wrap, i.e. the width is greater than 600px but less than 3300px), the boxes won’t fill up the entire width. What if we want to avoid that annoying whitespace? Add flex:1; to the testimonials class and notice that, however many items are in a row, they will stretch horizontally to fit. Taft, for instance, has more to say than some of the others, so lets give him some more room. Add

.taft { flex: 2; }

These numbers are proportions, so they do not have units. His testimonial won’t necessarily take up twice as much room as the others. Because his flex-grow property is twice as big as the others, the container will do its best to make his item’s width twice as wide, but may not succeed.

I’m introducing the shorthand property right up front here, because that’s probably what you’ll use most. What we’ve specified above is the flex-grow property, but that also implicitly sets the flex-basis to 0. There’s also a flex-shrink property. That really only works without wrapping, and is essentially the inverse of flex-grow, shrinking items proportionally (as available) to attempt to fit in the viewport. Feel free to turn off wrapping, remove the images, and play around with it, but I won’t include examples here. The default for that is 1, however. Anyway, let’s add a flex-basis of auto to the .testimonials class:

.testimonials {
  //...
  flex: 1 1 auto;
  //...
}

What happened? Taft’s impressive girth has gone away! His flex-basis, as I said, was implicitly set to 0, so what does that mean? It takes a look at the item’s width property (300px), ignores the whitespace, and uses that as its default size before distributing/flexing the item within the container. What the auto keyword does is takes a look at the item’s width property, then takes all the white space, and distributes that among the items proportionally.

Update the .taft class to flex: 2 1 auto;. Now the whitespace width he gets allocated is twice as big as the whitespace width allocated to the other .testimonials, or as close as your browser can get. If you want him to take up all the space, or as much as possible, without messing up the stretchiness of the bottom/wrapped row(s), set the flex-grow property (the first number) to something absurdly large, like 9999. This basically replicates what we had before we included flex-basis.

What if, instead of stretching the boxes, we wanted to put spacing between them? Take out the flex line in the testimonial, and in the container, add justify-content:space-evenly;. The extra whitespace on each line is split and placed in each gap. If there are four items in a row, and 240px of whitespace at the end, this will put 48px before the first item, 48px between the first and second, etc., and 48px after the last item in that row.

justify-content has some other keywords that can be used:

justify-content:space-evenly;
justify-content:flex-start; //this is the default
justify-content:flex-end; //lines up things at the end (right or bottom of most containers)
justify-content:center; //sounds promising...
justify-content:space-between;
justify-content:space-around;

space-between would take that theoretical 240px of whitespace on the end of a four-item line and place 80px between each item, but no whitespace before the first or after the last.

space-around takes that whitespace and divides it into 8 chunks, 24px each, and puts it before and after each item. Which means there ends up being twice as much whitespace between items as there is before the first or after the last.

All of these can be quite useful in certain contexts. For this one, space-between and space-evenly probably look the best.

And if you want to put all the whitespace after a specific element, add

margin-right:auto;

(or margin-left, or margin-top, or margin-bottom, whichever is relevant).

We’re not done yet! Notice that, for each row, the testimonial blocks stretch vertically to perhaps an absurd degree, based on whichever item has the most content. Another property, align-items, is here to help.

align-items: flex-start;
align-items: flex-end;
align-items: center;
align-items: baseline;
align-items: stretch; //default

Go ahead and play with these (in the container); some are more self-explanatory than others. Baseline is, perhaps, the confusing one. It will align all items with the baseline of the first nested element, which, in the example, is the images. This is why Bill Murray and the kitten have different image heights in the example. Play around some more by moving some text above the image, and watch how the result aligns. The bottom (baseline) of the first line of text is the line that’s used for alignment.

How about changing the alignment of just one item? The align-items property may be tempting, but it’s for use in a container. align-self is the one to use here. Add to your CSS file

.seagal { align-self:flex-end; }

and watch what happens. The same properties are available for these as with align-items, with the addition of auto, which is the default, and inherits the align-items property of its parent.

Almost finished. Order is something else that can be changed with flexbox using, oddly enough, the order property.

Say we want to move the zombie to the front. One way is to update the HTML to movie his testimonial up. Another way, if this were a data-driven site, would be to update the query such that the brain-eater was returned first. Or, you can do it using CSS.

Add this to the CSS file:

.zombie { order:-1; }

The default value of the order property is 0, so you’d either have to specify everything else with a higher number, or give the particular class a negative number. Either solution is fine.

One major caveat to using the order property: if a speech reader is being used, for example, this will not change the order content is aurally presented. But in a semantic layout, the content should be presented first if no stylesheet is used, thus, for instance, a navigation bar can be coded last in the HTML yet be presented first in a visual medium.

Try to use what you’ve learned to place text of one of the testimonials above the image. Hint: it involves the order property, the flex-flow property, and the display property.

How about the problem we started with? Perfectly centering an item, horizontally and vertically? Add the following:

.center-container {
	background-color:blue;
	display:flex;
	justify-content:center;
	align-items:center;
	width:95vw;
	height:95vh;
}
.centered {
	background-color:green;
	font-size:3em;
	padding:1em;
}

The highlighted lines are all that’s necessary; the width and height ensure we have a sufficiently large container to demonstrate the concept, and the .centered class adds some styling to the bit of graffiti.

Before I wrap this up, I’m going to address responsive images. I’ll cover them more comprehensively later, but we sometimes will want our images to take up the space they’ve been given  in case, for example, we want them to shrink to make room for Taft.

.testimonial img {
	height:auto;
	width:100%;
}

You’ll also probably want to add a min-width property to the testimonial class so that the images/items don’t shrink too much. Or a max-width if you don’t want Taft to get too big.

Resources & References

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.