An Interactive Guide to Flexbox in CSS • Josh W. Comeau

Content

Introduction

Flexbox is a remarkably powerful layout mode. When we truly understand how it works, we can build dynamic layouts that respond automatically, rearranging themselves as-needed.

For example, check this out:

This demo is heavily inspired by Adam Argyle’s incredible “4 layouts for the price of 1”(opens in new tab) 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;
}

I remember running into demos like this and being completely baffled. I knew the basics of Flexbox, but this seemed like absolute wizardry!

In this blog post, I want to refine your mental model for Flexbox. We'll build an intuition for how the Flexbox algorithm works, by learning about each of these properties. Whether you're a CSS beginner, or you've been using Flexbox for years, I bet you'll learn quite a bit!

Let's do this!

Content warning

I make a food-related metaphor later in this tutorial.

Way better on desktop!

This tutorial features a ton of interactive demos, and some of these demos just really don't work super great on smaller devices.

I've done my best to make them mobile-friendly, but for the best experience, I recommend going through this tutorial on a desktop, laptop, or tablet.

CSS is comprised of many different layout algorithms, known officially as “layout modes”. Each layout mode is its own little sub-language within CSS. The default layout mode is Flow layout, but we can opt in to Flexbox by changing the display property on the parent container:

When we flip display to flex, we create a “flex formatting context”. This means that, by default, all children will be positioned according to the Flexbox layout algorithm.

Clippy, the helpful paperclip assistant from Microsoft Word 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!

Link to this headingFlex direction

As mentioned, Flexbox is all about controlling the distribution of elements in a row or column. By default, items will stack side-by-side in a row, but we can flip to a column with the flex-direction property:

Show Primary Axis

With flex-direction: row, the primary axis runs horizontally, from left to right. When we flip to flex-direction: column, the primary axis runs vertically, from top to bottom.

In Flexbox, everything is based on the primary axis. The algorithm doesn't care about vertical/horizontal, or even rows/columns. All of the rules are structured around this primary axis, and the cross axis that runs perpendicularly.

This is pretty cool. When we learn the rules of Flexbox, we can switch seamlessly from horizontal layouts to vertical ones. All of the rules adapt automatically. This feature is unique to the Flexbox layout mode.

The children will be positioned by default according to the following 2 rules:

  1. Primary axis: Children will be bunched up at the start of the container.
  1. Cross axis: Children will stretch out to fill the entire container.

Here's a quick visualization of these rules:

In Flexbox, we decide whether the primary axis runs horizontally or vertically. This is the root that all Flexbox calculations are pegged to.

We can change how children are distributed along the primary axis using the justify-content property:

Show Primary Axis

flex-direction:

justify-content:

flex-start center flex-end space-between space-around space-evenly

When it comes to the primary axis, we don't generally think in terms of aligning a single child. Instead, it's all about the distribution of the group.

We can bunch all the items up in a particular spot (with flex-start, center, and flex-end), or we can spread them apart (with space-between, space-around, and space-evenly).

For the cross axis, things are a bit different. We use the align-items property:

Show Primary Axis

flex-direction:

justify-content:

flex-start center flex-end space-between space-around space-evenly

align-items:New

stretch flex-start center flex-end baseline

It's interesting… With align-items, we have some of the same options as justify-content, but there isn't a perfect overlap.

'justify-content'

'align-items'

space-between space-around space-evenly

flex-startcenterflex-end

Why don't they share the same options? We'll unravel this mystery shortly, but first, I need to share one more alignment property: align-self.

Unlike justify-content and align-items, align-self is applied to the child element, not the container. It allows us to change the alignment of a specific child along the cross axis:

Show Primary Axis

flex-direction:

align-self:

stretch flex-start center flex-end baseline

align-self has all the same values as align-items. In fact, they change the exact same thing. align-items is syntactic sugar, a convenient shorthand that automatically sets the alignment on all the children at once.

There is no justify-selfAt least, not in Flexbox. The property is implemented in the Grid layout mode.. To understand why not, we need to dig deeper into the Flexbox algorithm.

Link to this headingContent 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 Meat on a stick. AKA skewers, souvlaki, shish-kabob:

Illustration of a kebab: 3 pieces of meat horizontally skewered on a wooden stick. The stick is labeled 'Primary axis'

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 Bite-sized hot dogs / vienna sausages:

3 separate cocktail wieners, each with their own toothpick skewering them vertically. The toothpicks are labeled 'cross axis'

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:

This button inclues a draggable image of a wiener which can be dragged up and down to change its vertical position.

Drag me!

This button inclues a draggable image of a wiener which can be dragged up and down to change its vertical position.

This button inclues a draggable image of a wiener which can be dragged up and down to change its vertical position.

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 button inclues a draggable image of a kebab piece which can be dragged horizontally, but only a tiny bit, as it runs into the other pieces on the kebab.

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.

Link to this headingHypothetical size

Let's talk about one of the most eye-opening realizations I've had about Flexbox.

Suppose I have the following CSS:

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

Format code using Prettier

Focus the editor. This will trap focus until you press Escape.Code editor:

<div class\="flex-wrapper\">
<style> .flex-wrapper { display: flex; } .item { width: 2000px; } </style> <div class="item"></div> <div class="flex-wrapper"> <div class="item"></div> </div>

Resize editor. Use left/right arrows.

Result

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 Horrifying North American cartoon character known for bursting through walls.

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.

Link to this headingGrowing 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.

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.

Give it a shot here. Each child has been given flex-basis: 50px, but you can tweak the first child:

Show Primary Axis

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 and flex-basis interchangeably in a Flex row, but there are some exceptions. For example, the width property affects replaced elements like images differently than flex-basis. Also, width can reduce an item below its minimum size, while flex-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.

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:

0

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:

The first child wants 1 unit of extra space, while the second child wants 1 unit. That means the total # of units is 2 (1 + 1). Each child gets a proportional share of that extra space.

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:

flex-basis

300px

Reduced by:

0%

'flex-basis'

150px

Reduced by:

0%

Interesting, right? Both items shrink, but they shrink proportionally. The first child is always 2x the width of the second child. You might see some rounding issues that suggest it's off by a pixel or two. This is likely a flaw with my overengineered demo!

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.

flex-basis

250px

flex-shrink

1

Reduced by:

0%

flex-basis

250px

'flex-shrink'

1

Reduced by:

0%

Alright, so: we have two children, each with a hypothetical size of 250px. The container needs to be at least 500px wide to contain these children at their hypothetical size.

Let's suppose we shrink the container to 400px. Well, we can't stuff 500px worth of content into a 400px bag! We have a deficit of 100px. Our elements will need to give up 100px total, in order for them to fit.

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.

Now, let's suppose we crank that first child up to flex-shrink: 3:

Screenshot showing the demo above, locked at 400px wide, with the first child having a flex-shrink of 3

We have a total deficit of 100px. Normally, each child would pay ½, but because we've tinkered with flex-shrink, the first element winds up paying ¾ (75px), and the second element pays ¼ (25px).

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.

Shrinking and proportions

In the example we've been looking at, both Flex children have the same hypothetical size (250px). When figuring out how to shrink them, we can calculate it exclusively using flex-shrink.

As we saw earlier, though, the shrinking algorithm will also try and preserve the proportions between siblings. If the first child is 2x the size of the second, it will shrink more aggressively.

So, the full calculation involves looking at each child's relative flex-shrink and its relative size.

I had an epiphany a while back about flex-shrink: we can think of it as the “inverse” of flex-grow. They're two sides of the same coin:

  • flex-grow controls how the extra space is distributed when the items are smaller than their container.
  • flex-shrink controls how space is removed when the items are bigger than their container.

This means that only one of these properties can be active at once. If there's extra space, flex-shrink has no effect, since the items don't need to shrink. And if the children are too big for their container, flex-grow has no effect, because there's no extra space to divvy up.

I like to think of it as two separate realms. You're either on Earth, or in the The evil alternative dimension from Stranger Things. Each world has its own rules.

Link to this headingPreventing shrinking

Summary
这篇文章介绍了Flexbox布局模式的强大功能,强调了其在创建动态响应式布局中的重要性。作者通过示例展示了如何使用Flexbox而无需媒体查询,利用流体原则实现无缝布局。文章详细解释了Flexbox的基本概念,包括主轴和交叉轴的定义,以及如何通过`flex-direction`、`justify-content`和`align-items`等属性控制元素的排列和对齐。作者指出,尽管CSS Grid也很强大,但Flexbox在处理动态、流体用户界面时仍然具有优势。文章还通过比喻帮助读者理解Flexbox的工作原理,强调了内容与项目之间的区别。总之,文章旨在帮助读者更好地理解和运用Flexbox布局,提升其CSS技能。