An Interactive Guide to Flexbox
https://www.joshwcomeau.com/css/interactive-guide-to-flexbox/
This demo is heavily inspired by Adam Argyle’s incredible “4 layouts for the price of 1” codepen. It uses no media/container queries. Instead of setting arbitrary breakpoints, it uses fluid principles to create a layout that flows seamlessly.
Here's the relevant CSS:
form {
display: flex;
align-items: flex-end;
flex-wrap: wrap;
gap: 16px;
}
.name {
flex-grow: 1;
flex-basis: 160px;
}
.email {
flex-grow: 3;
flex-basis: 200px;
}
button {
flex-grow: 1;
flex-basis: 80px;
}
form {
display: flex;
align-items: flex-end;
flex-wrap: wrap;
gap: 16px;
}
.name {
flex-grow: 1;
flex-basis: 160px;
}
.email {
flex-grow: 3;
flex-basis: 200px;
}
button {
flex-grow: 1;
flex-basis: 80px;
}
Introduction to Flexbox
...
Each layout algorithm is designed to solve a specific problem. The default “Flow” layout is meant to create digital documents; it's essentially the Microsoft Word layout algorithm. Headings and paragraphs stack vertically as blocks, while things like text, links, and images sit inconspicuously within these blocks.
So, what problem does Flexbox solve? Flexbox is all about arranging a group of items in a row or column, and giving us a ridiculous amount of control over the distribution and alignment of those items. As the name suggests, Flexbox is all about flexibility. We can control whether items grow or shrink, how the extra space is distributed, and more.
Is it still relevant?
You might be wondering: now that CSS Grid is well-supported in modern browsers, isn't Flexbox obsolete?
CSS Grid is a wonderful layout mode, but it solves different problems than Flexbox. We should learn both layout modes, and use the right tool for the job.
Flexbox still reigns supreme when it comes to dynamic, fluid UIs that arrange items in a vertical or horizontal list. We'll see an example in this guide, the deconstructed pancake, that can't easily be accomplished with CSS Grid.
Honestly, as someone comfortable with both CSS Grid and Flexbox, I still find myself reaching for Flexbox quite often!
Alignment
...
Content vs. items
So, based on what we've learned so far, Flexbox might seem pretty arbitrary. Why is it justify-content
and align-items
, and not justify-_items_
, or align-_content_
?
For that matter, why is there an align-self
, but not a _justify_-self
??
These questions get at one of the most important and misunderstood things about Flexbox. To help me explain, I'd like to use a metaphor.
In Flexbox, items are distributed along the primary axis. By default, they're nicely lined up, side-by-side. I can draw a straight horizontal line that skewers all of the children, like a kebab?:
The cross axis is different, though. A straight vertical line will only ever intersect one of the children.
It's less like a kebab, and more like a group of cocktail wieners?:
There's a significant difference here. With the cocktail wieners, each item can move along its stick without interfering with any of the other items:
By contrast, with our primary axis skewering each sibling, a single item can’t move along its stick without bumping into its siblings! Try dragging the middle piece side to side:
This is the fundamental difference between the primary/cross axis. When we're talking about alignment in the cross axis, each item can do whatever it wants. In the primary axis, though, we can only think about how to distribute the group.
That's why there's no justify-self
. What would it mean for that middle piece to set justify-self: flex-start
? There's already another piece there!
With all of this context in mind, let's give a proper definition to all 4 terms we've been talking about:
justify
— to position something along the primary axis.align
— to position something along the cross axis.content
— a group of “stuff” that can be distributed.items
— single items that can be positioned individually.
And so: we have justify-content
to control the distribution of the group along the primary axis, and we have align-items
to position each item individually along the cross axis. These are the two main properties we use to manage layout with Flexbox.
There's no justify-items
for the same reason that there's no justify-self
; when it comes to the primary axis, we have to think of the items as a group, as content that can be distributed.
What about align-content
? Actually, this does exist within Flexbox! We'll cover it a little later on, when we talk about the flex-wrap
property.
Hypothetical size
Let's talk about one of the most eye-opening realizations I've had about Flexbox.
Suppose I have the following CSS:
.item {
width: 2000px;
}
A reasonable person might look at this and say: “alright, so we'll get an item that is 2000 pixels wide”. But will that always be true?
Let's test it:
Code Playground
<style>
.flex-wrapper {
display: flex;
}
.item {
width: 2000px;
}
</style>
<div class="item"></div>
<div class="flex-wrapper"><div class="item"></div></div>
This is interesting, isn't it?
Both items have the exact same CSS applied. They each have width: 2000px
. And yet, the first item is much wider than the second!
The difference is the layout mode. The first item is being rendered using Flow layout, and in Flow layout, width
is a hard constraint. When we set width: 2000px
, we'll get a 2000-pixel wide element, even if it has to burst through the side of the viewport like the Kool-Aid guy.
In Flexbox, however, the width
property is implemented differently. It's more of a suggestion than a hard constraint.
The specification has a name for this: the hypothetical size. It's the size an element would be, in a perfect utopian world, with nothing getting in the way.
Alas, things are rarely so simple. In this case, the limiting factor is that the parent doesn't have room for a 2000px-wide child. And so, the child's size is reduced so that it fits.
This is a core part of the Flexbox philosophy. Things are fluid and flexible and can adjust to the constraints of the world.
Inputs for the algorithms
We tend to think of the CSS language as a collection of properties, but I think that's the wrong mental model. As we've seen, the
width
property behaves differently depending on the layout mode used!Instead, I like to think of CSS as a collection of layout modes. Each layout mode is an algorithm that can implement or redefine each CSS property. We provide an algorithm with our CSS declarations (key/value pairs), and the algorithm decides how to use them.
In other words, the CSS we write is an input for these algorithms, like arguments passed to a function. If we want to truly feel comfortable with CSS, it's not enough to learn the properties; we have to learn how the algorithms use these properties.
This is the central philosophy taken by my course, CSS for JavaScript Developers. Rather than have you memorize a bunch of inscrutable CSS snippets, we pop the hood on the language and learn how all of the layout modes work.
Growing and shrinking
So, we've seen that the Flexbox algorithm has some built-in flexibility, with hypothetical sizes. But to really see how fluid Flexbox can be, we need to talk about 3 properties: flex-grow
, flex-shrink
, and flex-basis
.
Let's look at each property.
flex-basis
I admit it: for a long time, I didn't really understand what the deal was with flex-basis
. 😅
To put it simply: In a Flex row, flex-basis
does the same thing as width
. In a Flex column, flex-basis
does the same thing as height
.
As we've learned, everything in Flexbox is pegged to the primary/cross axis. For example, justify-content
will distribute the children along the primary axis, and it works exactly the same way whether the primary axis runs horizontally or vertically.
width
and height
don't follow this rule, though! width
will always affect the horizontal size. It doesn't suddenly become height
when we flip flex-direction
from row
to column
.
And so, the Flexbox authors created a generic “size” property called flex-basis
. It's like width
or height
, but pegged to the primary axis, like everything else. It allows us to set the hypothetical size of an element in the primary-axis direction, regardless of whether that's horizontal or vertical.
Like we saw with width
, flex-basis
is more of a suggestion than a hard constraint. At a certain point, there just isn't enough space for all of the elements to sit at their assigned size, and so they have to compromise, in order to avoid an overflow.
Not exactly the same
In general, we can use
width
andflex-basis
interchangeably in a Flex row, but there are some exceptions. For example, thewidth
property affects replaced elements like images differently thanflex-basis
. Also,width
can reduce an item below its minimum size, whileflex-basis
can't.It's well outside the scope of this blog post, but I wanted to mention it, because you may occasionally run into edge-cases where the two properties have different effects.
flex-grow
By default, elements in a Flex context will shrink down to their minimum comfortable size along the primary axis. This often creates extra space.
We can specify how that space should be consumed with the flex-grow property.
The default value for flex-grow
is 0, which means that growing is opt-in. If we want a child to gobble up any extra space in the container, we need to explicitly tell it so.
What if multiple children set flex-grow
? In this case, the extra space is divvied up between children, proportionally based on their flex-grow
value.
I think it'll be easier to explain visually. Try incrementing/decrementing each child. https://www.joshwcomeau.com/css/interactive-guide-to-flexbox/#flex-grow
flex-shrink
In most of the examples we've seen so far, we've had extra space to work with. But what if our children are too big for their container?
Let's test it. Try shrinking the container to see what happens: https://www.joshwcomeau.com/css/interactive-guide-to-flexbox/#flex-shrink
Interesting, right? Both items shrink, but they shrink proportionally. The first child is always 2x the width of the second child.*
As a friendly reminder, flex-basis
serves the same purpose as width
. We'll use flex-basis
because it's conventional, but we'd get the exact same result if we used width
!
flex-basis
and width
set the elements' hypothetical size. The Flexbox algorithm might shrink elements below this desired size, but by default, they'll always scale together, preserving the ratio between both elements.
Now, what if we don't want our elements to scale down proportionally? That's where the flex-shrink
property comes in.
Take a couple of minutes and poke at this demo. See if you can figure out what's going on here. We'll explore below. https://www.joshwcomeau.com/css/interactive-guide-to-flexbox/#flex-shrink
The flex-shrink
property lets us decide how that balance is paid.
Like flex-grow
, it's a ratio. By default, both children have flex-shrink: 1
, and so each child pays ½ of the balance. They each forfeit 50px, their actual size shrinking from 250px to 200px.
Note that the absolute values don't matter, it's all about the ratio. If both children have flex-shrink: 1
, each child will pay ½ of the total deficit. If both children are cranked up to flex-shrink: 1000
, each child will pay 1000/2000 of the total deficit. Either way, it works out to the same thing.
Preventing shrinking
Sometimes, we don't want some of our Flex children to shrink.
I notice this all the time with SVG icons and shapes. Let's look at a simplified example: https://www.joshwcomeau.com/css/interactive-guide-to-flexbox/#preventing-shrinking
When the container gets narrow, our two circles get squashed into gross ovals. What if we want them to stay circular?
We can do this by setting flex-shrink: 0
:
https://www.joshwcomeau.com/css/interactive-guide-to-flexbox/#preventing-shrinking
A simpler approach?
So, I teach this concept in my course, and every now and then, someone will wonder why we're going through all the trouble of using
flex-shrink
when there's a simpler approach available:
.item.ball {
min-width: 32px;
}
A few years ago, I would have agreed. If we set a minimum width, the item won't be able to shrink below that point! We're adding a hard constraint, instead of the soft constraint of
width
/flex-basis
.I think this is one of those situations where it's easy to confuse “familiar” with “simple”. You're probably much more comfortable with
min-width
thanflex-shrink
, but that doesn't meanflex-shrink
is more complicated!After a few years of practice, I actually feel like setting
flex-shrink: 0
is the more straightforward / direct solution to this particular problem. Though,min-width
still has an important role to play in the Flexbox algorithm! We'll talk about that next.
The minimum size gotcha
There's one more thing we need to talk about here, and it's super important. It may be the single most helpful thing in this entire article!
Let's suppose we're building a fluid search form for an e-commerce store: https://www.joshwcomeau.com/css/interactive-guide-to-flexbox/#the-minimum-size-gotcha
When the container shrinks below a certain point, the content overflows!
But why?? flex-shrink
has a default value of 1
, and we haven't removed it, so the search input should be able to shrink as much as it needs to! Why is it refusing to shrink?
Here's the deal: In addition to the hypothetical size, there's another important size that the Flexbox algorithm cares about: the minimum size.
The Flexbox algorithm refuses to shrink a child below its minimum size. The content will overflow rather than shrink further, no matter how high we crank flex-shrink
!
Text inputs have a default minimum size of 170px-200px (it varies between browsers). That's the limitation we're running into above.
Here's the good news: We can redefine the minimum size with the min-width
property.
Proceed with caution
It's worth noting that the built-in minimum size does serve a purpose. It's meant to act as a guardrail, to prevent something even worse from happening.
For example: when we apply
min-width: 0px
to our text-containing Flex children, things break in an even worse way: https://www.joshwcomeau.com/css/interactive-guide-to-flexbox/#the-minimum-size-gotcha
With great power comes great responsibility, and
min-width
is a particularly powerful property when it comes to Flexbox. It's gotten me out of a jam more than once, but I'm always careful to make sure I'm not making things worse!
Auto margins
There's one other spacing-related trick I want to share. It's been around since the early days of Flexbox, but it's relatively obscure, and it blew my mind when I first discovered it.
The margin
property is used to add space around a specific element. In some layout modes, like Flow and Positioned, it can even be used to center an element, with margin: auto
.
Auto margins are much more interesting in Flexbox: https://www.joshwcomeau.com/css/interactive-guide-to-flexbox/#auto-margins
Auto margins will gobble up the extra space, and apply it to the element's margin. It gives us precise control over where to distribute the extra space.
Wrapping
So far, all of our items have sat side-by-side, in a single row/column. The flex-wrap
property allows us to change that.
Each row is its own mini Flexbox environment. align-items
will move each item up or down within the invisible box that wraps around each row.
But what if we want to align the rows themselves? We can do that with the align-content
property: https://www.joshwcomeau.com/css/interactive-guide-to-flexbox/#wrapping
To summarize what's happening here:
flex-wrap: wrap
gives us two rows of stuff.- Within each row,
align-items
lets us slide each individual child up or down - Zooming out, however, we have these two rows within a single Flex context! The cross axis will now intersect two rows, not one. And so, we can't move the rows individually, we need to distribute them as a group.
- Using our definitions from above, we're dealing with content, not items. But we're also still talking about the cross axis! And so the property we want is
align-content
.