这是用户在 2024-6-14 23:57 为 https://www.joshwcomeau.com/css/interactive-guide-to-flexbox/ 保存的双语快照页面,由 沉浸式翻译 提供双语支持。了解如何保存?
Skip to content 跳到内容

An Interactive Guide to Flexbox
Flexbox 交互式指南

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.
Flexbox 是一种非常强大的布局模式。当我们真正了解它是如何工作的时,我们可以构建自动响应的动态布局,并根据需要重新排列。

For example, check this out:
例如,请查看以下内容:

600
Drag me! 拖我!

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.
这个演示的灵感来自亚当·阿盖尔(Adam Argyle)令人难以置信的“4种布局,价格为1”代码笔。它不使用媒体/容器查询。它不是设置任意断点,而是使用流体原理来创建无缝流动的布局。

Here's the relevant CSS: 以下是相关的 CSS:

css

I remember running into demos like this and being completely baffled. I knew the basics of Flexbox, but this seemed like absolute wizardry!
我记得遇到这样的演示时,我完全感到困惑。我知道 Flexbox 的基础知识,但这似乎是绝对的魔法!

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!
在这篇博文中,我想完善你对 Flexbox 的心智模型。我们将通过了解这些属性中的每一个来构建 Flexbox 算法如何工作的直觉。无论你是CSS初学者,还是已经使用Flexbox多年,我敢打赌你会学到很多东西!

Let's do this! 让我们开始吧!

Link to this heading
Introduction to Flexbox Flexbox 简介

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:
CSS由许多不同的布局算法组成,正式称为“布局模式”。每个布局模式都是 CSS 中自己的小子语言。默认布局模式是 Flow 布局,但我们可以通过更改父容器上的 display 属性来选择加入 Flexbox:

Hello 你好
to
the
world! 世界!
Primary Axis 主轴
Primary Axis 主轴
display: 显示:

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.
当我们 display 翻转到 flex 时,我们创建了一个“flex formatting context”。这意味着,默认情况下,所有子项都将根据 Flexbox 布局算法进行定位。

Clippy, the helpful paperclip assistant from Microsoft WordEach 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.
每种布局算法都旨在解决特定问题。默认的“流”布局旨在创建数字文档;它本质上是Microsoft Word布局算法。标题和段落作为块垂直堆叠,而文本、链接和图像等内容则不显眼地位于这些块中。

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.
那么,Flexbox解决了什么问题呢?Flexbox 就是将一组项目排列成一行或一列,并让我们对这些项目的分布和对齐进行荒谬的控制。顾名思义,Flexbox 是关于灵活性的。我们可以控制物品是增长还是缩小,额外空间的分配方式等等。

Link to this heading
Flex 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:
如前所述,Flexbox 就是控制一行或一列中元素的分布。默认情况下,项将并排堆叠成一行,但我们可以翻转到具有以下属性的 flex-direction 列:

Hi there! 嘿,你好!
to
the
world 世界
Primary Axis 主轴
Primary Axis 主轴
flex-direction: 弹性方向:

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.
对于 flex-direction: row ,主轴从左到右水平延伸。当我们翻转到 flex-direction: column 时,主轴从上到下垂直运行。

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.
在 Flexbox 中,一切都基于主轴。该算法不关心垂直/水平,甚至不关心行/列。所有规则都是围绕这个主轴和垂直运行的十字轴构建的。

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.
这很酷。当我们了解 Flexbox 的规则时,我们可以从水平布局无缝切换到垂直布局。所有规则都会自动调整。此功能是 Flexbox 布局模式所独有的。

The children will be positioned by default according to the following 2 rules:
默认情况下,将按照以下 2 条规则对子项进行定位:

  1. Primary axis: Children will be bunched up at the start of the container.
    主轴:儿童将被束在容器的起点。
  2. 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.
在 Flexbox 中,我们决定主轴是水平运行还是垂直运行。这是所有 Flexbox 计算都与此挂钩的根。

Link to this heading
Alignment 对准

We can change how children are distributed along the primary axis using the justify-content property:
我们可以使用以下 justify-content 属性更改子项沿主轴的分布方式:

Hello 你好
to
the
World 世界
Primary Axis 主轴
Primary Axis 主轴
row
flex-start

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:

Hello
to
the
World
Primary Axis
Primary Axis
row
flex-start
stretch

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
flex-start
center
flex-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:

Hello
to
the
world
Primary Axis
Primary Axis
row
stretch

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-self. To understand why not, we need to dig deeper into the Flexbox algorithm.

Link to this heading
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 :

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 :

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:

Drag me!

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.

Link to this heading
Hypothetical size

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

Suppose I have the following CSS:

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

Result

Enable ‘tab’ key

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 .

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.

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

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:

Primary Axis
Primary Axis
flex-direction:
50

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.

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:

Extra Space
Primary Axis
Primary Axis
flex-direction:
flex-grow:

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:

flex-grow:
1
2
flex-grow:
1
2
Primary Axis
Primary Axis

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
Actual size:
300px
Reduced by:
0%
flex-basis
150px
Actual size:
150px
Reduced by:
0%
Primary Axis
Primary Axis
600

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.

flex-basis
250px
flex-shrink
1
Actual size:
250px
Reduced by:
0%
flex-basis
250px
flex-shrink
1
Actual size:
250px
Reduced by:
0%
Primary Axis
Primary Axis
1
600

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.


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 . Each world has its own rules.

Link to this heading
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:

Primary Axis
Primary Axis
600

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:

Primary Axis
Primary Axis
flex-shrink:
600

When we set flex-shrink to 0, we essentially “opt out” of the shrinking process altogether. The Flexbox algorithm will treat flex-basis (or width) as a hard minimum limit.

Here's the full code for this demo, if you're curious:

Flex Shrink ball demo

Result

Enable ‘tab’ key

Link to this heading
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:

500

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.

In other cases, the limiting factor might be the element's content. For example, try resizing this container:

The longest word in this item is “sesquipedalian”.
This one has no long words.
Primary Axis
Primary Axis
600

For an element containing text, the minimum width is the length of the longest unbreakable string of characters.

Here's the good news: We can redefine the minimum size with the min-width property.

min-width:
500

By setting min-width: 0px directly on the Flex child, we tell the Flexbox algorithm to overwrite the “built-in” minimum width. Because we've set it to 0px, the element can shrink as much as necessary.

This same trick can work in Flex columns with the min-height property (although the problem doesn't seem to come up as often).

One of the biggest Flexbox quality-of-life improvements in recent years has been the gap property:

Primary Axis
Primary Axis
flex-direction:
4px

gap allows us to create space in-between each Flex child. This is great for things like navigation headers:

Primary Axis
Primary Axis
justify-content:
16px

gap is a relatively new addition to the Flexbox language, but it's been implemented across all modern browsers since early 2021.

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:

Primary Axis
Primary Axis
margin-left:
margin-right:

Earlier, we saw how the flex-grow property can gobble up any extra space, applying it to a child.

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.

A common header layout features the logo on one side, and some navigation links on the other side. Here's how we can build this layout using auto margins:

Code Playground

Result

Enable ‘tab’ key

The Corpatech logo is the first list item in the list. By giving it margin-right: auto, we gather up all of the extra space, and force it between the 1st and 2nd item.

We can see what's going on here using the browser devtools:

Screenshot showing the above playground with an orange rectangle representing margin between the first item and the others

There are lots of other ways we could have solved this problem: we could have grouped the navigation links in their own Flex container, or we could have grown the first list item with flex-grow. But personally, I love the auto-margins solution. We're treating the extra space as a resource, and deciding exactly where it should go.

Phew! We've covered a lot of stuff so far. There's just one more big takeaway I want to share.

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.

Check it out:

flex-basis
180px
Actual size
180px
flex-basis
180px
Actual size
180px
flex-basis
180px
Actual size
180px
Primary Axis
Primary Axis
flex-wrap:
600

Most of the time when we work in two dimensions, we'll want to use CSS Grid, but Flexbox + flex-wrap definitely has its uses! This particular example showcases the “deconstructed pancake” layout, where 3 items stack into an inverted pyramid on mid-sized screens.

When we set flex-wrap: wrap, items won't shrink below their hypothetical size. At least, not when wrapping onto the next row/column is an option!

But wait! What about our kebab / cocktail weenie metaphor??

With flex-wrap: wrap, we no longer have a single primary axis line that can skewer each item. Effectively, each row acts as its own mini flex container. Instead of 1 big skewer, each row gets its own skewer:

All of the rules we've learned so far continue to apply, within this reduced scope. justify-content, for example, will distribute the two pieces on each stick.

But hmm... How does align-items work, now that we have multiple rows? The cross axis could intersect multiple items now!

Take a moment to consider. What do you think will happen when we change this property? Once you have your answer (or at least an idea), see if it's right:

Primary Axis
Primary Axis
align-items:

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:

Primary Axis
Primary Axis
flex-start
flex-start

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.

So I want to acknowledge something: this has been a dense tutorial. We've gone way down the rabbit hole, and unless you're already a Flexbox pro, I expect your head is spinning a bit. 😅

Like so much in CSS, Flexbox might seem simple when you first get started, but the complexity ramps up quickly when you get beyond the basics.

As a result, so many of us hit an early plateau with CSS. We know enough to get things done, but it's a constant struggle. The language feels rickety and unpredictable, like an ancient rope bridge that could give out at any second. When it snaps, we hurl random StackOverflow snippets at the problem, hoping something will help.

It's no fun. And that sucks, since CSS is a pretty big part of most front-end dev jobs!

The thing is, CSS is actually a deeply robust and consistent language. The problem is that most of our mental models are incomplete and inaccurate. When we take the time to build a proper intuition for the language, things start to click, and CSS becomes an absolute joy to use. ✨

Last year, I released a comprehensive course called CSS for JavaScript Developers.

The course is “multi-modality”, meaning that it uses lots of different forms of media. I built my own course platform from scratch, so that it supports:

  • Interactive articles (like this blog post!)
  • Videos (170+ short videos)
  • Exercises
  • Real-world-inspired projects
  • Mini-games

If you found this blog post helpful, you'll love the course. It follows a similar approach, but for the entire CSS language, and with exercises and projects to make sure you're actually developing new skills.

It's specifically built for folks who use a JS framework like React/Angular/Vue. 80% of the course focuses on CSS fundamentals, but we also see how to integrate those fundamentals into a modern JS application, how to structure our CSS, stuff like that.

If you struggle with CSS, I hope you'll check it out. Gaining confidence with CSS is game-changing, especially if you're already comfortable with HTML and JS. When you complete the holy trinity, it becomes so much easier to stay in flow, to truly enjoy developing web applications.

Link to this heading
Bonus: Unpacking the demo

At the start of this tutorial, we saw the following “4 layouts for the price of 1” demo:

600
Drag me!

Now that we've learned all about the Flexbox algorithm, can you figure out how this works? Feel free to experiment with the code here:

Code Playground

Result

Enable ‘tab’ key

Let's walk through how this works:

Thanks so much for reading! This blog post was a ton of work, and I'm thrilled to have it out in the world! I hope you found it useful. 💖


Last Updated

June 27th, 2023

Hits

3D portrait of the blog's author, Josh Comeau

A front-end web development newsletter that sparks joy

My goal with this blog is to create helpful content for front-end web devs, and my newsletter is no different! I'll let you know when I publish new content, and I'll even share exclusive newsletter-only content now and then.

No spam, unsubscribe at any time.



If you're a human, please ignore this field.

3D portrait of the blog's author, Josh Comeau

Hi friend! Hope I didn't startle you. Can I let you know about my newsletter?
嗨,朋友!希望我没有吓到你。我可以让您知道我的时事通讯吗?