← Front-End Developer — all chapters  ·  Kelick Tech Hub

Chapter 9 — Layout with Flexbox

Section 1 — What Flexbox is for

Flexbox is a CSS layout tool for arranging items in a row or a column. It shines when you have a line of things — a navigation bar, a row of cards, a set of buttons — and you want them spaced, aligned, or stretched without fiddly maths. Before Flexbox arrived, developers built these layouts with floats, inline blocks, and carefully calculated margins, and the results were fragile: a slightly longer word, a slightly narrower screen, and the whole row would break. Centring something vertically was famously difficult, often requiring hacks that only worked in one browser or at one exact height. Flexbox was designed specifically to end that struggle, giving the browser itself the job of distributing space and lining things up.

The key idea is that Flexbox works in one direction at a time. You choose a direction — across or down — and the browser arranges the items along that line. That single-direction focus is both its strength and its limit. It makes Flexbox brilliant for components: menus, toolbars, card rows, form rows, footers, and anything else that is essentially a line of boxes. It also means Flexbox is not the right tool for a full page grid where rows and columns must line up with each other at the same time; that job belongs to CSS Grid, which you will meet in the next chapter. Knowing which tool fits which job is half of layout skill, and the rule of thumb is simple: one direction, Flexbox; two directions, Grid.

A helpful way to think about Flexbox is to compare it with ordinary block layout. In block layout, elements stack vertically and each one takes the full width available, and you have very little say in how leftover space is shared. In Flexbox, the container takes charge: it looks at how much space it has, how much space its items want, and then distributes the difference according to rules you set. Items can grow to fill spare room, shrink to fit a tight space, wrap onto new lines, or sit centred with equal space around them. You describe the intention — "spread these out", "centre these", "make these equal" — and the browser does the arithmetic, recalculating whenever the screen size changes or content is added. That is why Flexbox layouts feel robust: they adapt instead of breaking.

Consider a navigation bar with a logo on the left and four links on the right. With Flexbox, you make the bar a flex container, push the two groups apart, and centre everything vertically, in about three declarations. If a link is added later, or the logo is replaced with a wider one, the layout adjusts by itself. Try the example below in a browser, then change the text of one link to something much longer and watch the bar cope. That resilience is the reason Flexbox became a core skill for every front-end developer, and why this chapter spends fifty sections on it: the individual properties are simple, but combining them fluently takes practice.

.row {
  display: flex;
}

Check: What kind of layout is Flexbox best suited to — a row of items, or a full two-dimensional page grid?

Section 2 — The flex container and its items

When you set display: flex on an element, that element becomes a flex container, and its direct children become flex items. Only direct children count, and this detail matters more than it first appears. If your container holds a list, and that list holds list items, the list itself is the flex item — the list items inside it are not, and Flexbox properties on the container will not move them. To arrange the list items, you would make the list itself a flex container as well. Flexbox works one level at a time, and every level you want arranged needs its own container declaration. Once you internalise this, many confusing layouts suddenly make sense: the property you set seems to do nothing only because you set it on the wrong level.

The relationship between container and items is a division of labour. The container owns the big decisions: the direction items flow in, how they are spaced along that direction, how they are aligned across it, and whether they wrap. The items own decisions about themselves: how much they grow, how much they shrink, their starting size, and whether one item should align differently from its siblings. Most Flexbox properties therefore come in pairs with similar names — justify-content on the container and justify-self elsewhere, align-items on the container and align-self on the item — and mixing up which side of the relationship a property belongs to is the most common beginner error. A good habit is to ask, before writing any flex property, "am I describing the whole group, or one member of it?" The answer tells you where the declaration goes.

It is also worth knowing what happens to items when they become flex items. They stop behaving like ordinary blocks: they no longer take the full width of their parent, they no longer stack vertically by default, and their margins no longer collapse with neighbouring margins the way block margins do. Instead, each item is sized by its content and by the flex rules, and placed by the container. Text inside an item still flows normally, and elements inside an item still use normal layout unless you turn the item itself into a container. This is why nesting is so common in Flexbox layouts: a card can be a flex item in a row of cards and, at the same time, a flex container arranging its own title, text, and button in a column. The two roles do not conflict, because they operate at different levels.

Look at the navigation example below. The nav element is the container, and the three links are the items. If you wrapped the links in a div, that div would become the single flex item, and the links would stack inside it as normal until you also made the div a flex container. Try both versions in a browser and compare. Spotting which element is the container and which elements are the items is the single most useful debugging skill in this chapter, and developer tools can help: most browsers mark flex containers with a badge in the inspector, which you will use in Section 47.

<nav class="menu">
  <a href="#">Home</a>
  <a href="#">About</a>
  <a href="#">Contact</a>
</nav>

Check: In a flex container, which elements become flex items — all descendants, or only direct children?

Section 3 — Turning Flexbox on

The single declaration display: flex switches an element into flex layout, and on its own it already does something visible: the children line up in a row, side by side, instead of stacking vertically as block elements normally do. You do not need any other property to see Flexbox working, and that makes it easy to experiment. Create a page with a div containing three short paragraphs, add display: flex to the div, reload, and the paragraphs will sit in a row. Remove the declaration and they stack again. That immediate feedback loop — change one thing, see one change — is the best way to learn layout, and you should use it throughout this chapter rather than only reading the examples.

There is a sibling value worth knowing: display: inline-flex. It creates a flex container that behaves like an inline element on the outside, sitting within a line of text or beside other inline content, while still arranging its children with Flexbox on the inside. Ordinary display: flex makes the container a block-level box, taking the full width available. In practice, inline-flex is used less often — most containers you build will be block-level, such as a navigation bar, a card row, or a toolbar that spans its parent. But if you ever need a small flex group to sit inline, for example a row of badges inside a paragraph, inline-flex is the answer, and knowing it exists saves you from wrapping things in extra elements.

One more detail: display: flex affects only the layout of the container's children. It does not change the children themselves into anything special, and it does not change how the container is positioned on the page beyond making it block-level. The container still takes its width from its parent, still respects margins and padding, and still participates in the surrounding layout normally. This separation is useful, because it means you can turn Flexbox on for one group of elements without disturbing the rest of the page. If a layout goes wrong, you can also remove display: flex temporarily to see the unstyled stacking order underneath, which often reveals a nesting mistake — an element you thought was a child turning out to be a grandchild, for instance.

A common early confusion is setting display: flex on the items instead of the container. When you do that, each item becomes a container for its own children, and the row you wanted never forms, because nothing told the parent to arrange its children in a row. If your flex properties seem to do nothing, check first that the declaration is on the parent. The example below is the smallest possible flex layout; type it yourself, open it in a browser, and then try adding a fourth link, a longer word, and a gap to see how the row responds. Small experiments like this build the intuition that reading alone cannot.

.menu {
  display: flex;
}

Check: What is the one declaration that turns an element into a flex container?

Section 4 — The main axis and the cross axis

Every flex container has two axes, and almost every Flexbox property is defined in terms of them, so learning them first makes the rest of the chapter much easier. The main axis runs in the direction items flow — across the row, by default, from left to right. The cross axis runs at right angles to it — down the page, by default, from top to bottom. When you change the direction, the axes swap roles: in a column container, the main axis runs vertically and the cross axis runs horizontally. The properties do not change their names when this happens, which is exactly why they are named after axes rather than after "horizontal" and "vertical": justify-content always works on the main axis, whatever direction that happens to be, and align-items always works on the cross axis.

This is also the source of the most common Flexbox confusion. A beginner centres a row of items horizontally with justify-content: center, then switches the container to a column and is surprised that the items are now centred vertically instead. Nothing went wrong: the main axis moved, so the property followed it. The fix is to think in axes, not in screen directions. Before writing an alignment property, say to yourself which axis you mean: "along the flow" is justify, "across the flow" is align. If you can answer that, you will pick the right property nearly every time, in rows and columns alike.

The axes also explain the start and end language used throughout Flexbox. The main start is where items begin flowing — the left in a row, the top in a column — and the main end is where they finish. The cross start and cross end work the same way across the flow. Values like flex-start and flex-end refer to these points, not to the left or top of the screen, which is why they keep working correctly in right-to-left languages and in reversed directions. When a page is displayed in a language read right to left, the main start moves to the right, and flex-start follows it — a small detail that makes Flexbox naturally better for international layouts than properties tied to physical directions.

Try a concrete experiment to fix the axes in your mind. Build a container with three boxes of different heights, set it to a row, and apply justify-content: space-between and align-items: center. Watch the boxes spread horizontally and centre vertically. Now change only the direction to column and reload: the same two declarations now spread the boxes vertically and centre them horizontally. Nothing else changed, yet the result rotated. That is the axes at work, and once you have seen it happen, the naming of the properties will feel obvious rather than arbitrary. Keep this mental image for the whole chapter, because every alignment property from here on will be described in axis terms.

/* main axis: left to right; cross axis: top to bottom */
.row {
  display: flex;
}

Check: If items flow left to right, which way does the cross axis run?

Section 5 — flex-direction: row

flex-direction: row is the default direction: items flow from left to right along the main axis, in the order they appear in the HTML. You rarely need to write it, because it is what you get automatically when you set display: flex, but knowing it exists matters for two reasons. First, it makes the other directions understandable as variations: column turns the flow vertical, and the reverse values flip it. Second, there are times when you want to state it explicitly for clarity — for example, in a media query that switches a container back to a row after it was a column on small screens, or in code shared with other developers where being explicit prevents misunderstanding. Explicit is not wrong; it is documentation.

Row direction suits any group of things that reads left to right: navigation links, a row of cards, a toolbar, a set of tabs, or a header with a title on one side and a button on the other. The items sit on one line by default, which means a row container with many wide items can overflow on a narrow screen unless you allow wrapping — a topic covered in Sections 15 and 16. For now, notice the default behaviour: items keep their natural widths, sit side by side, and stretch to the container's height because of the default cross-axis alignment. That stretching is why a row of cards often looks neat without further work, and it is a behaviour you will use deliberately in Section 30.

It is worth pausing on what "left to right" means in a page read right to left. In such pages, the row direction starts at the right and flows left, because the main start follows the writing direction of the document. Flexbox respects this automatically, which is one reason it is preferred over older techniques that hardcoded left and right. If you ever build a site in Arabic, Hebrew, or another right-to-left language, your flex rows will mirror themselves without extra code, as long as you used flex properties rather than physical margins and floats. This is a quiet advantage that only becomes visible when you need it, but it is a good example of why learning the axis-based model pays off beyond the immediate layout problem.

A useful exercise is to build a row and deliberately vary its contents: give one item a long sentence, another a single word, and a third an image. Watch how the row accommodates them: each item takes the width its content needs, and the total row width is the sum. Then add a fourth item and shrink the browser window until the row overflows. That overflow is not a bug — it is the default nowrap behaviour telling you that you have not yet decided what should happen when space runs out. The rest of this chapter is, in large part, about making that decision well: wrapping, growing, shrinking, and spacing, all chosen deliberately rather than left to chance.

.row {
  display: flex;
  flex-direction: row;
}

Check: Which way do flex items flow with flex-direction: row?

Section 6 — flex-direction: column

flex-direction: column turns the main axis vertical, so items stack top to bottom instead of sitting side by side. This is handy for any stack of things you still want Flexbox to space and align: a list of form fields, a column of buttons, a card whose title, text, and button should be arranged vertically, or a whole page split into header, content, and footer. The important shift is mental rather than visual — a column container still looks like ordinary stacked blocks at first glance, but now the container controls the spacing and alignment of the stack, and properties like justify-content work vertically instead of horizontally. That control is what makes the column direction worth using even when the layout looks simple.

In a column, the roles of the alignment properties swap compared with a row. justify-content now distributes space vertically: space-between pushes the first item to the top and the last to the bottom, and center gathers the stack in the middle of the container's height. align-items now works horizontally: center centres each item across the width, and stretch — still the default — makes each item as wide as the container. This swap catches people out regularly, and the remedy is always the same: think in axes. Ask which way the items flow, and you know which axis is main. If you remember nothing else from this section, remember that a column container's "justify" is vertical, because that single fact resolves a large number of confused layouts.

Column direction is also the foundation of several larger patterns you will build later in this chapter. A page laid out as a flex column, with the main area set to grow, gives you the sticky footer of Section 37. A card built as a flex column, with its button pushed to the bottom, gives you the aligned buttons of Section 30. A form built as a column with a gap gives you evenly spaced fields without a single margin. In each case, the column direction is doing quiet, structural work: it turns a stack into something the container manages, so that spacing, alignment, and growth are decisions you make once on the container instead of adjustments you repeat on every child.

One caution applies to column containers and height. A row container's height is usually set by its tallest item, but a column container needs a height to distribute space within — if the container is only as tall as its contents, there is no spare vertical space for justify-content to share, and values like space-between will appear to do nothing. That is not a failure of the property; there is simply nothing to distribute. When you want vertical distribution, give the container a height, often with min-height: 100vh for a page or a fixed height for a panel, and the spacing will appear. This principle — alignment properties need spare space to be visible — applies to rows too, and it is worth checking whenever an alignment value seems to have no effect.

.stack {
  display: flex;
  flex-direction: column;
}

Check: When the direction is column, which axis is the main axis?

Section 7 — Reverse directions

row-reverse flows items right to left, and column-reverse flows them bottom to top. They flip the visual order without changing the HTML, which can be useful in specific situations — for example, when a design calls for an image on the left and text on the right, but the HTML puts the text first because it is the more important content for screen readers and search engines. In that case, reversing the visual order lets the design and the document order each be right in their own way. The properties are simple to use: one declaration on the container reverses the whole line, and the start and end of the main axis swap with it, so justify-content: flex-start now packs items at the right in a reversed row.

The reason to use reverse directions sparingly is that they create a gap between what sighted users see and what everyone else experiences. Screen readers read the HTML order, keyboard users tab through the HTML order, and search engines weigh the HTML order. If the visual order says "image, then heading, then button" but the HTML order says "heading, then button, then image", a keyboard user will reach the button before ever encountering the image that the visual design used to explain it. That mismatch is confusing, and in the worst cases it makes a page genuinely harder to use. The same caution applies to the order property in Section 25 and to the reversed wrapping of Section 17. Reversal is a tool, not a habit, and it should be used when there is a real reason — such as keeping the most meaningful content first in the document — rather than as a casual way to shuffle a layout.

There is also a subtle interaction with wrapping and alignment that is worth knowing. In a reversed row, the main start is on the right, so flex-start packs items to the right and space-between still puts the first HTML item at the main start — which is now the right-hand side. The first item in the HTML is still "first" in flex terms; it is just displayed at the opposite end. This consistency is helpful once you expect it, because it means the flex model does not change when you reverse: only the direction of the axis changes, and every other property follows the axis faithfully. If a reversed layout surprises you, check where the main start now sits, and the rest usually falls into place.

A practical guideline: prefer to get the HTML order right first, and only then consider reversing. If the design and the document order agree, you do not need reverse at all. If they disagree, ask whether the HTML order can be changed to match the design without harming meaning — often it can, and that is the cleaner solution. Reserve row-reverse and column-reverse for the cases where the document order must stay as it is for accessibility or content reasons, and the visual order genuinely needs to differ. Used that way, reversal is a thoughtful accessibility technique rather than a shortcut, and you will be able to explain why it is there when someone else reads your code.

.reversed {
  display: flex;
  flex-direction: row-reverse;
}

Check: Does row-reverse change the order of elements in the HTML?

Section 8 — justify-content: start and end

justify-content controls how items are spread along the main axis, and its simplest values are flex-start and flex-end. flex-start packs the items at the main start — the left in a normal row, the top in a column — and leaves any spare space at the far end. flex-end does the opposite, packing items at the main end and leaving the spare space at the start. These are the values that produce the familiar "everything on the left" and "everything on the right" arrangements, and they are worth understanding precisely because every other justify value is a variation on how spare space is shared rather than left over.

The phrase "spare space" deserves emphasis, because justify-content only has a visible effect when there is spare space to distribute. If the items exactly fill the container, or overflow it, then flex-start and flex-end look identical to each other in one sense — there is nothing to move — and beginners sometimes conclude the property is broken. It is not: with no spare space, packing at the start and packing at the end produce the same full row. To see the difference, the container must be wider than its items, which is the normal case for a navigation bar inside a full-width header, but not for a row of cards that already fills its parent. When a justify value seems to do nothing, checking whether spare space exists is the first diagnostic step.

There is also a newer pair of values, start and end, which behave like flex-start and flex-end except that they follow the writing direction in a slightly different way in some edge cases. In ordinary left-to-right pages the difference is invisible, and flex-start and flex-end remain the values you will see in most code and most teaching. You do not need to memorise the distinction now; just recognise start and end when you encounter them in other people's stylesheets, and know they belong to the same family. Consistency within your own code matters more than which spelling you choose, so pick one pair and use it throughout a project.

In practice, flex-start is the default and often goes unwritten, while flex-end is the workhorse for right-aligned groups: a row of action buttons at the bottom of a dialog, a "Log out" link pushed to the end of a toolbar, or pagination controls sitting at the right of a bar. Combined with the auto-margin trick of Section 27, you can also mix the two in one container — most items packed at the start and one item pushed to the end — which covers a large number of real header and toolbar designs. The pattern to remember is small: justify is the main axis, start and end are the two ends of that axis, and everything else in the justify family is about what happens to the space in between.

.row {
  display: flex;
  justify-content: flex-end;
}

Check: With a row direction, where does justify-content: flex-end put the items?

Section 9 — justify-content: center

justify-content: center gathers the items in the middle of the main axis, with equal spare space on either side. It is the simplest way to centre a row of buttons, a set of navigation links, or a group of tabs, and it has a property that older centring techniques lacked: it stays centred automatically. Add an item, remove an item, change the text length, resize the window — the group recentres itself each time, because the browser recalculates the spare space and splits it evenly on every layout. That self-adjusting quality is what makes Flexbox centring feel effortless compared with the fixed margins and manual calculations it replaced.

Centring a group is not the same as centring a single item, though the same value serves both. With one item, justify-content: center places it in the middle of the container along the main axis; pair it with align-items: center and you get the full two-dimensional centring of Section 28, the classic "dead centre" layout used for hero sections, empty states, and login panels. With several items, the group as a whole is centred, and the items keep their natural spacing relative to each other — or the spacing set by gap, if you have set one. This distinction matters when you combine centring with gap: the gaps are part of the group's width, so the group including its gaps is what gets centred, and the result looks balanced rather than offset.

It is worth comparing justify-content: center with the older text-align: center approach, because beginners sometimes use the two interchangeably. text-align: center centres inline content, such as text and images, inside a block, and it works on the container's text rather than on its child boxes. It will centre the words inside a button, for example, but it will not centre a row of buttons themselves, because buttons are not text. justify-content: center works on flex items, which are boxes, and that is why it is the right tool for centring a group of elements. If you ever find text-align: center failing to centre a row of boxes, the reason is almost always that the boxes are not inline content, and switching the parent to a flex container with centred justification will fix it.

A final practical note: centred groups can overflow awkwardly on very narrow screens, because the group is centred and both ends can spill out equally, hiding the first item off the left edge where it cannot be scrolled to. If your centred row might overflow — a long navigation bar on a phone, for instance — consider allowing wrapping, or switching to a different justification at small widths with a media query. Centring is a statement about how spare space is shared, and like every statement in this chapter, it works best when there is actually spare space to share. When space runs out, wrapping and shrinking, covered in later sections, take over.

.centered-row {
  display: flex;
  justify-content: center;
}

Check: What does justify-content: center do with the spare space in a container?

Section 10 — justify-content: space-between

space-between pushes the first item to the main start and the last item to the main end, and shares whatever space is left evenly between the items. If there are three items, there are two gaps between them, and those two gaps are equal. If there are two items, there is one gap, and the items sit at opposite ends. The result is a layout with no space at the outer edges and even spacing inside — the classic shape of a header with a logo on the left and navigation on the right, a footer with a copyright notice on one side and links on the other, or a card header with a title and a button at opposite ends.

The most important thing to understand about space-between is how it behaves with different numbers of items, because that behaviour surprises people. With a single item, there is no "between" at all, so the item sits at the main start and the value looks identical to flex-start. With two items, it looks like a deliberate split across the container. With many items, it looks like even distribution. This means a layout that uses space-between can change character dramatically when items are added or removed — a navigation bar with five links looks evenly spread, but the same bar with one link looks left-aligned. If your design must look the same regardless of item count, space-around, space-evenly, or a fixed gap with another justification may suit it better. space-between is at its best when the item count is stable and the design genuinely wants the ends pinned to the edges.

It also interacts with wrapping in a way worth knowing. When a space-between container wraps onto several lines, each line is justified independently: the last line, if it holds fewer items, will spread those items across the full width, which can look odd — two cards on the last row stretched to opposite edges while the rows above hold four evenly spaced cards. This is correct behaviour by the specification, but it is rarely the design anyone wanted. The usual remedies are to let the last row keep its natural spacing by using gap with flex-start instead, or to accept the spread as a deliberate look. Knowing this in advance saves a debugging session later, because the spread last row is one of those effects that looks like a bug but is actually the property doing exactly what it was asked to do.

To use space-between well, pair it with the right cross-axis alignment — usually align-items: center for a header or toolbar, so the logo, links, and buttons share a vertical centre — and with a gap only if you want a minimum spacing as well. Note that gap and space-between combine: the gaps are added first, and the remaining space is then distributed between items, so the final gaps are the gap value plus the distributed share. For most headers you will want one or the other, not both, and choosing deliberately keeps the spacing predictable. Try the header example below, resize the window, and add and remove links to feel how the distribution responds.

.header {
  display: flex;
  justify-content: space-between;
}

Check: With space-between, where do the first and last items sit?

Section 11 — space-around and space-evenly

space-around gives each item equal space on both sides of itself, so the gap between two neighbouring items is the sum of two half-spaces — double the space at the outer ends, where only one item contributes. space-evenly makes every gap identical, including the gaps between the first item and the container edge and between the last item and the edge. The difference is subtle, and in many layouts it is barely noticeable, but it is real, and it is easiest to see at the ends of the row: with space-around, the end spaces are half the size of the inner gaps; with space-evenly, all gaps match. If you cannot tell which one a design uses, look at the ends.

Choosing between them is mostly a matter of the look you want. space-around gives a slightly tucked-in feeling, because the items sit a little closer to the edges than to each other, which suits groups that should feel contained — a row of social icons in a footer, for example. space-evenly gives a more airy, mathematical feeling, with the group floating in evenly measured space, which suits a row of feature cards or a set of steps in a process. Neither is more correct in general; they are two flavours of "spread out, but not pinned to the edges", and the contrast with space-between is the point: where space-between has no space at the edges at all, both of these keep space at the edges, differing only in how that edge space compares with the inner gaps.

Both values share the behaviour of space-between when the item count changes or when lines wrap: each line is justified independently, and a single item on a line will sit with its space distributed around it rather than pinned to an edge. That makes them more forgiving than space-between for variable item counts — a lone item on the last row of a wrapping space-evenly container sits roughly centred rather than stretched to an edge — but it also means they are not the right choice when you need items pinned to the edges. As with all justify values, the question to ask is what should happen to the spare space: left over at one end, split between items, wrapped around each item, or made perfectly even. Answer that, and the value chooses itself.

A practical way to learn the family is to build one row of four coloured boxes and cycle through all five justify values — flex-start, flex-end, center, space-between, space-around, and space-evenly — watching the ends of the row each time. The ends are the tell: no space, half space, equal space, or full space. Once you can predict where the ends will fall for each value, you have the justify family mastered, and the rest of the chapter becomes a matter of combining it with alignment, wrapping, and sizing. Keep that experiment handy, because you will return to it whenever a new layout needs its spacing decided.

.even-row {
  display: flex;
  justify-content: space-evenly;
}

Check: How does space-evenly differ from space-around at the ends of the row?

Section 12 — align-items: stretch

align-items controls items along the cross axis, and its default value is stretch. In a row container, stretch makes every item as tall as the container — or, more precisely, as tall as the tallest item, since the container's height is usually set by its tallest content. That is why cards in a flex row often come out equal in height without any extra work: nobody set their heights, and yet they match, because the default alignment stretched the shorter ones. It is one of those behaviours that feels like magic until you know the default, and then it feels obvious.

Stretch only works when the item has no fixed size on the cross axis. If a card has an explicit height, or a height set by another rule, stretch will not override it — the item keeps its height and is positioned according to the alignment rules as if stretch did not apply. Similarly, if the container has a fixed height taller than all its items, stretch will grow every item to fill it, which is sometimes exactly what you want — a sidebar and a main area both filling the full height of a page row, for example — and sometimes a surprise, when a small badge inside a tall bar suddenly becomes as tall as the bar. In that case, the fix is to set the item's own alignment, with align-self: center or similar, as Section 26 explains. The default is a starting point, not a prison.

The practical value of stretch shows up most in card layouts and in split layouts. A row of product cards with different amounts of text looks tidy when the cards share a height, because their borders line up and their buttons can then be aligned to the bottom, as Section 30 shows. A two-column layout where the sidebar stretches to match the main content looks intentional rather than accidental. And in a column container, stretch works horizontally: items fill the container's width, which is why form fields in a flex column naturally share a width without you setting one. In each case, stretch is doing the job of making unlike things line up, which is the heart of what alignment properties are for.

It is worth noting that stretch respects an item's minimum and maximum sizes. An item with max-height will stretch only up to that maximum, and one with min-height will not shrink below it. This interaction is usually helpful — it lets you say "stretch, but not beyond this" — but it can puzzle you if a max-height set elsewhere quietly caps the stretching. When stretched items refuse to stretch, check for a height, min-height, or max-height on the item or on a rule that targets it. Alignment problems in Flexbox are very often sizing constraints in disguise, and learning to look for the constraint first will make your debugging faster across this whole chapter.

.cards {
  display: flex;
  align-items: stretch;
}

Check: With a row direction and align-items: stretch, what happens to the height of shorter items?

Section 13 — align-items: center, start and end

align-items: center lines items up in the middle of the cross axis, flex-start lines them up at the cross start, and flex-end at the cross end. In a row, that means centred vertically, at the top, or at the bottom. In a column, it means centred horizontally, at the left, or at the right. These three values, plus stretch, cover nearly every cross-axis alignment you will need, and center is by far the most used of them, because vertically centring things inside bars, headers, and cards is one of the most common layout tasks on the web — and one of the tasks Flexbox made dramatically easier than the techniques that came before it.

Consider a navigation bar containing a logo image, some text links, and a button. Without Flexbox, getting the text, the image, and the button to share a vertical centre requires fiddly line-height tricks or vertical-align adjustments that break as soon as a font size changes. With Flexbox, align-items: center on the bar does it regardless of the items' heights, and it keeps working when the logo is swapped for a taller one or the button text wraps. The same applies to a card with an icon beside its text, a form row with a label beside an input, or a list item with a thumbnail beside a title. Whenever two unlike things sit side by side and should share a centre, this is the property.

flex-start and flex-end have their own uses. Top-aligning cards that contain different amounts of text, so that their titles line up even though their heights differ, is a flex-start job. Bottom-aligning a set of buttons or prices so they share a baseline edge is a flex-end job. The choice depends on which edge should be the shared reference: if the tops should line up, start; if the bottoms should line up, end; if the middles should line up, center. Stating it that way makes the choice mechanical rather than a matter of trial and error, and it works in columns too, with left, right, and centre taking the place of top, bottom, and middle.

A common mistake is to set align-items on an item rather than the container, or to expect it to do something when the container's cross size equals the items' size. Like justify-content, align-items needs spare cross space to be visible: if the container is exactly as tall as its items, centring and top-aligning look the same, because there is nowhere to move. Give the container extra height — a taller bar, a fixed-height panel — and the alignment becomes visible. When an align value seems to do nothing, check two things: is it on the container, and is there spare space on the cross axis? One of those answers is almost always the explanation.

.bar {
  display: flex;
  align-items: center;
}

Check: Which align-items value centres items on the cross axis?

Section 14 — align-items: baseline

baseline lines items up by the baseline of their text — the invisible line that letters sit on. If items have different font sizes, baseline alignment makes their first lines of text line up neatly, where center would line up their boxes instead and the text would look ragged. Imagine a row containing a large heading, a small paragraph, and a tiny caption: with center, the boxes are centred and the text sits at three different heights; with baseline, the text shares one line and the boxes end up at different heights, which is usually what the eye wants. The eye reads text, not boxes, so aligning the text is what makes the row look right.

Baseline alignment works on the first line of text in each item. For items that contain no text, or whose text is nested deeply, the browser falls back to a sensible approximation based on the item's content, but the results are less predictable — baseline is at its best with text-bearing items like headings, paragraphs, and labels. It also has a quirk worth knowing: items with different amounts of text, or with text that wraps, still align on their first baseline, so a two-line paragraph will align its first line with a neighbouring single line, and the second line will hang below. That is correct behaviour, but it means baseline is best for rows where each item contributes roughly one line of text, such as a heading beside a price, a label beside a value, or a row of mixed-size headings.

A classic use case is a price display: a large currency amount beside a smaller "per month" note, or a product name in a large font beside a small badge. With baseline alignment, the words sit on the same line and the display looks professionally typeset, even though the font sizes differ wildly. Another is a form where labels of different sizes sit beside inputs: baseline alignment keeps the label text level with the input's text, which feels more natural than centring the whole label box against the input box. In both cases, the alternative — centre alignment — would produce a subtle wrongness that is hard to name but easy to feel, and switching to baseline fixes it in one word.

Baseline is also a good illustration of how Flexbox thinks about content rather than boxes. Most layout systems align boxes; Flexbox, in this one value, aligns the text inside the boxes, because the people who designed it understood that rows of text are judged by their text. Keep baseline in your toolkit for the specific situation it serves — mixed font sizes, one line of text each, alignment that should please the eye rather than the ruler — and use center or flex-start everywhere else. Knowing when not to use a value is as much a part of mastery as knowing when to use it.

.mixed-text {
  display: flex;
  align-items: baseline;
}

Check: When would baseline be a better choice than center?

Section 15 — flex-wrap: nowrap

By default, flex-wrap is nowrap: items stay on one line even if they overflow the container. If the items are too wide together, they squeeze — shrinking according to their shrink values — or, if they cannot shrink enough, they spill out beyond the container's edge. That is fine for a navigation bar, where you usually want a single line and would rather handle narrow screens with a different pattern, such as a menu button or a direction change. But it is the wrong behaviour for a row of cards, where spilling out means content cut off or a horizontal scrollbar, and where dropping to a new line is clearly what the design wants.

Understanding nowrap means understanding that Flexbox, left to itself, prefers one line. The container does not wrap "when it gets crowded" on its own; it keeps a single line until you tell it otherwise. This default surprises beginners who expect wrapping to be automatic, because wrapping is automatic for text and for inline elements. Flex items are different: they are laid out by the container's explicit rules, and the explicit rule says one line unless told otherwise. Once you expect that, the behaviour becomes predictable, and you will start every row layout by asking yourself the wrapping question deliberately: should this row stay on one line, or should it wrap?

When a nowrap row overflows, the browser's response follows a sequence worth knowing. First, items shrink, if their shrink values allow it, down toward their content minimum sizes. Then, if they still do not fit, they overflow visibly, spilling past the container in the direction of the flow. The container itself does not grow to contain them, and no scrollbar appears unless an ancestor has overflow scrolling set. The result is content that may be cut off by the edge of the viewport, which is bad for usability and for accessibility, since overflowed content can be unreachable by keyboard or screen reader in some situations. Overflow is therefore a signal, not a state to leave in place: it tells you the row needs wrapping, shrinking limits, smaller items, or a different layout at that width.

A useful habit is to test every nowrap row at a narrow width early, before the layout grows complicated. Shrink the browser window to a phone width and look at each row: does it still fit, does it shrink gracefully, or does it spill? If it spills, decide now whether to wrap it, restyle it for small screens with a media query, or redesign it — the three standard answers, covered in Sections 16 and 41. Catching overflow early is far cheaper than discovering it at the end of a project, when fixing one row can disturb five others. The nowrap default is not a flaw; it is a question the browser asks you, and answering it deliberately is part of building a layout.

.single-line {
  display: flex;
  flex-wrap: nowrap;
}

Check: What happens when flex items are too wide for a nowrap container?

Section 16 — flex-wrap: wrap

flex-wrap: wrap lets items drop onto a new line when they run out of room, like words wrapping in a paragraph. Combined with a sensible item width, this gives you a card layout that shows several cards per row on a wide screen and fewer on a narrow one, with no media query required for the wrapping itself. The browser handles the decision: it fills the first line with as many items as fit, then starts a second line, and so on, until every item is placed. Resize the window and the lines reflow, which is the responsive behaviour most card and gallery layouts want, achieved with a single declaration.

Wrapping only works well when items have a size to wrap against. If items have no width or basis and are allowed to shrink freely, they may simply squeeze onto one line instead of wrapping, because shrinking is tried before wrapping in the layout process. That is why wrapping rows almost always pair flex-wrap: wrap with a flex-basis on the items — "aim for this width, and wrap when you cannot have it" — plus a gap for the gutters. The pattern from Section 32, a container with wrap and gap and items with flex: 1 1 220px, is the standard recipe, and it is worth memorising as a unit rather than as separate properties, because the pieces only work properly together.

Each wrapped line is laid out independently. Justification applies per line, so a line that holds fewer items — usually the last line — is justified on its own, which is why the space-between last-line effect from Section 10 happens. Alignment across lines is handled by align-content, covered in Section 38, which decides how the lines themselves are spread along the cross axis. And each line's height is set by its tallest item, with the items in that line aligned within it according to align-items. Thinking of a wrapped container as a stack of independent rows, each laid out by the same rules, makes its behaviour much easier to predict than thinking of it as one complicated grid.

Wrapping is also the simplest answer to the narrow-screen problem for many components. A toolbar that wraps keeps all its buttons reachable on a phone, where a nowrap toolbar would overflow. A row of filter chips that wraps stays usable at any width. A set of tags, a gallery, a card row — all of these are wrapping rows by nature, and making them wrap from the start means they never need a special small-screen redesign. The design question is not "should this wrap?" in the abstract, but "what should this look like when it wraps?" — and the answers are usually a gap, a basis, and a decision about the last line's justification. Get those three right, and wrapping layouts mostly take care of themselves.

.card-row {
  display: flex;
  flex-wrap: wrap;
}

Check: What does flex-wrap: wrap do when items run out of horizontal room?

Section 17 — flex-wrap: wrap-reverse

wrap-reverse wraps items like wrap, but new lines are added in the opposite cross direction — for a row, extra lines stack upwards instead of downwards. The first line sits at the cross end rather than the cross start, and each additional line is placed before it, so the visual order of lines is reversed while the order of items within each line stays the same. It is the wrapping counterpart of the reverse directions from Section 7, and it completes the picture: wrapping, like the main axis, has a direction, and that direction can be flipped.

In practice, wrap-reverse is rarely needed, and it is worth being honest about that. Most layouts want new lines below the previous ones, which is what wrap gives you. The cases for wrap-reverse are unusual: a component anchored to the bottom of a panel, where new lines should grow upwards from the anchor; a chat-like display where the newest content sits at the bottom and older lines stack above it; or a design that deliberately mirrors the usual flow for visual reasons. If you cannot name a reason like one of those, you probably want wrap, and reaching for wrap-reverse would only confuse the next person who reads the code. Rarity is not a criticism of the value — it exists for the layouts that genuinely need it — but it is a reason to pause before using it.

The interaction with alignment is where wrap-reverse most often surprises people. Because the cross start and cross end swap roles for line placement, values like align-content: flex-start now pack the lines at the opposite edge from where you might expect, and flex-end packs them at the other. The rule is consistent — start and end always refer to the cross axis as the container defines it, and wrap-reverse redefines which physical edge is the start — but the visual result can feel backwards until you remember the flip. If a wrap-reverse layout looks upside down, check your align-content and align-items values against the flipped cross axis before assuming something else is wrong.

There is also a relationship with the order property and with reverse directions that is worth keeping straight. wrap-reverse reverses the order of lines, not the order of items within a line, and not the HTML order. An item that is first in the HTML is still first within its line; it is the lines themselves that stack in reverse. If you need both the lines and the items reversed, you would combine wrap-reverse with a reversed direction, but that combination is genuinely rare and should be a sign to reconsider whether the HTML order itself should change. As with all reversal tools in this chapter, the guiding question is whether the visual order and the document order are telling the same story, and wrap-reverse should not be used to make them tell different ones without a good reason.

.upward-wrap {
  display: flex;
  flex-wrap: wrap-reverse;
}

Check: With a row direction, where do new lines go under wrap-reverse?

Section 18 — gap

gap puts a fixed space between items, without adding space at the ends. row-gap sets the space between rows and column-gap the space between columns; plain gap sets both, with the row value first if you give two values, as in gap: 12px 24px. It is far cleaner than the old technique of putting margins on every item and then removing the margin from the last one in each row — a technique that required fragile selectors, broke when items wrapped, and left stray margins whenever the item count changed. Gap is part of the container's layout, not part of the items, so it adapts automatically when items are added, removed, or wrapped.

The difference between gap and margin is worth stating precisely, because beginners often treat them as interchangeable. A margin belongs to an item: it travels with the item, it adds to the item's outer size, and it appears at the ends of a row as well as between items. A gap belongs to the container: it exists only between items, never at the outer edges, and it does not change any item's size. That makes gap the right choice for the space between items in a layout, and margin the right choice for the space around a whole component or for the auto-margin trick of Section 27. When you want "space between these things", reach for gap first; when you want "push this thing away from that thing", margin may still be the answer. The distinction keeps spacing predictable, because you always know which mechanism is responsible for which space.

Gap also interacts neatly with the other spacing properties. In a space-between container, gaps are added first and the remaining space is then distributed, so the final spacing is gap plus distributed share — usually a reason to use one or the other, not both, as Section 10 noted. In a wrapping container, row-gap controls the space between lines and column-gap the space between items on a line, which is exactly the control you need for a card grid: horizontal gutters and vertical gutters can differ, and both stay consistent as the number of columns changes. And because gap is not part of any item, it does not interfere with sizing calculations: an item with flex: 1 and a container with a gap will share the space minus the gaps, which is the result you want, whereas margins on flex items can make the arithmetic surprising.

Browser support for gap in Flexbox is now universal in modern browsers, though it arrived later for Flexbox than for Grid — older tutorials sometimes claim gap does not work in flex containers, and that was true years ago but is no longer the case. If you are supporting very old browsers, margins remain the fallback, but for new work, gap is the standard. Use it for every row of cards, toolbar, navigation list, and form group in this chapter's examples, and you will find your layouts need fewer overrides, fewer special cases, and fewer "remove the margin from the last item" hacks. That reduction in special cases is the real benefit: gap makes spacing a property of the group, which is where it conceptually belongs.

.card-row {
  display: flex;
  flex-wrap: wrap;
  gap: 16px;
}

Check: Why is gap tidier than margins for spacing flex items?

Section 19 — How flex items size themselvesA flex item starts at its natural size: as wide as its content needs, or as wide as its `width` if one is set. Flexbox only grows or shrinks items when the container has spare space or too little space, and only within the rules set by the next few properties. Content size is always the starting point, and that principle explains a great deal of Flexbox behaviour. A button with short text starts narrow; a card with a long paragraph starts wide; an image starts at its natural pixel width. The container then looks at the sum of those starting sizes, compares it with its own size, and decides whether to grow, shrink, or leave things alone.

This starting size has a technical name — the flex base size — and it is determined by flex-basis when one is set, and by the item's content or width otherwise. The distinction matters because the grow and shrink calculations work from the base size, not from the final size. If three items start at 100, 200, and 300 pixels, and the container has 300 pixels of spare space to share equally, each item grows by 100 and they end at 200, 300, and 400 — the original differences are preserved, because growth is added to the base, not used to equalise. If you wanted equal final sizes regardless of content, you would set the base to zero, which is what flex: 1 does, as Section 24 explains. Understanding that growth is added to a starting point, rather than replacing it, prevents the common surprise of "I set them all to grow equally, but they came out different widths" — they did grow equally; they just started differently.

The practical consequence is that sizing a flex layout is a two-step thought process. First, what should each item's natural or starting size be — its content, a width, or a basis? Second, what should happen when the container is bigger or smaller than the sum of those sizes — grow, shrink, wrap, or overflow? Beginners often jump to the second question and set grow values everywhere, without noticing that the first question already determined most of the result. Spend a moment on the starting sizes — set a basis on cards, let buttons size to their text, give a sidebar a fixed basis — and the grow and shrink rules become small adjustments rather than heroic efforts. The container does the arithmetic, but you choose the starting numbers, and good starting numbers make the arithmetic come out right.

.item {
  width: 120px;
}

A common mistake is to assume that a flex item can always shrink to nothing when space is tight. In practice, every item has a floor set by its content. A long word that cannot break, a wide image, or a button with padding will resist shrinking past that floor, and the container will overflow rather than crush the item. That is why a row that looks fine with short labels can break when a real product name or a real user name arrives.

Check: What is a flex item's starting size before growing or shrinking is applied?

Section 20 — flex-grow`flex-grow` says how an item shares out spare space. A value of `0` means "do not grow", and it is the default — items keep their starting size when there is spare room, unless you say otherwise. If every item has `flex-grow: 1`, they split the spare space equally, each receiving the same addition. If one item has `2` and another has `1`, the first gets twice as much of the spare space as the second. The numbers are proportions, not pixels, and not percentages: `flex-grow: 2` does not mean "twice as wide as the other item", it means "twice as much of the spare space", added to whatever starting size the item already had. That distinction, from Section 19, is the whole secret of flex-grow.

A concrete example makes the proportions clear. Suppose a container is 900 pixels wide, holding two items that start at 100 and 200 pixels. The spare space is 600 pixels. If both items have flex-grow: 1, each gets 300, and they end at 400 and 500. If the first has flex-grow: 2 and the second has 1, the spare space is divided into three parts of 200: the first gets two parts (400) and ends at 500, the second gets one part (200) and ends at 400. Notice that the item with the larger grow value did not end up twice as wide — it received twice as much growth. To get equal final widths from unequal starting widths, you need equal starting widths too, which is why flex: 1 sets the basis to zero before growing, as Section 24 explains. Grow values distribute the leftover; they do not erase the starting point.

The classic uses of flex-grow are the main content area beside a fixed sidebar, which grows to fill whatever width remains; a search input in a toolbar that expands while its buttons stay fixed; and a row of equal columns, where every item grows equally from a zero basis. In each case, the pattern is the same: fixed elements get flex-grow: 0 (or simply no grow declaration, since zero is the default), and the flexible element gets a positive grow value. If several elements should share the growth, give them all the same value; if one should grow faster, give it a larger one. Keep the numbers small — 1 and 2 cover nearly every real layout — because large numbers like 7 and 13 make the proportions hard to reason about, and a future reader of your code will thank you for the restraint.

.main {
  flex-grow: 2;
}

.side {
  flex-grow: 1;
}

Grow values also interact with wrapping in a way that is easy to miss. On a wrapped row, spare space is shared within each line separately, not across the whole container. A lone item on the last line therefore receives all of the spare space for that line if it has a positive grow value, which can make it stretch far wider than the items above it.

Check: If one item has flex-grow: 2 and another has flex-grow: 1, how is spare space shared?

Section 21 — flex-shrink`flex-shrink` is the mirror image of flex-grow: it says how an item gives up space when the container is too small. The default is `1`, meaning the item may shrink in proportion to its shrink value and its starting size. A value of `0` means the item refuses to shrink and may overflow the container instead. Like grow, the numbers are proportions, but the shrink calculation is weighted by starting size as well as by the shrink value, so a wide item with `flex-shrink: 1` gives up more pixels than a narrow item with the same value — it has more to give, and the browser takes proportionally from each item's size. The result is that items shrink in a way that roughly preserves their relative proportions, rather than all losing the same number of pixels.

Shrink also has a limit that surprises people: an item will not shrink below its content minimum size, as Section 19 introduced. A button containing the word "Subscribe" cannot shrink narrower than that word, no matter what its shrink value says, because the browser will not squash text unreadably by default. When a row overflows despite generous shrink values, this floor is usually the reason: the items have shrunk to their floors and still do not fit. The remedies are to let the row wrap, to shorten the content, to allow the text to wrap or truncate (Section 40), or to reduce the number of items at that width. Fighting the floor by forcing smaller sizes generally damages readability, so treat the floor as information — the content is telling you it needs more room or a different layout — rather than as an obstacle to override.

It helps to think of grow and shrink as a pair of answers to a pair of questions. When there is too much room, who grows? When there is too little room, who shrinks? A well-designed flex row answers both: the main content grows and shrinks freely, the fixed elements grow not at all and shrink not at all, and the secondary elements shrink a little but not below readability. Writing both values explicitly, even when one of them is the default, makes the intention readable — flex: 1 1 200px says "start at 200, grow if there is room, shrink if there is not" in a single line, and a future reader can see the whole sizing story at a glance. The shorthand is covered in Section 23, but the thinking behind it starts here, with the two questions and their two answers.

.fixed-badge {
  flex-shrink: 0;
}

A shrink value larger than 1, such as flex-shrink: 2, makes an item give up space twice as fast as a sibling with 1, which is useful when a secondary panel should yield before the main content does.

Check: What does flex-shrink: 0 promise an item?

Section 22 — flex-basis`flex-basis` is the item's starting size before growing or shrinking, along the main axis. In a row it behaves like a width; in a column, like a height. Setting `flex-basis: 200px` on cards in a wrapping row says "aim for 200 pixels wide, then grow or shrink from there". It is the item's preferred size, the number the flex calculations start from, and it is the most important of the three sizing properties to understand, because grow and shrink are meaningless without a starting point to grow or shrink from. If you set only a grow value and no basis, the basis defaults to the item's content size, which may be fine for a button but is rarely what you want for a card.

The relationship between flex-basis and width confuses many learners, and the distinction is worth nailing down. width sets the item's final width in ordinary layout, and in a flex item it still influences the starting size when no basis is set. But once a basis is set, the basis wins: it becomes the starting size for flex maths, and width is effectively ignored for that purpose. Think of width as a statement about the box, and flex-basis as a statement about the flex calculation. In a flex item, you usually want the flex statement, because it participates in growing, shrinking, and wrapping, whereas a plain width can fight with those behaviours. A practical rule: inside a flex container, set flex-basis instead of width when you mean "this is the size to start from", and reserve width for cases where you genuinely want a fixed box.

Flex-basis also accepts the keyword auto, which means "use my width, or my content size if no width is set", and content, which means "use my content size regardless of any width". The default value of the flex shorthand's basis is auto in some forms and 0% in others, which is why flex: 1 and flex: auto behave differently — a difference Section 45 explores. For now, the useful mental model is simple: flex-basis is the "aim for" size, grow says what happens with extra room, shrink says what happens with too little room, and together the three describe the complete sizing behaviour of an item. When a layout misbehaves, checking the basis first often reveals the problem: a card that refuses to wrap may have a basis larger than the container, or a column that looks uneven may have bases set by content of different lengths.

.card {
  flex-basis: 200px;
}

A basis can also be a percentage, such as flex-basis: 30%, which means thirty percent of the container's main-axis size. That is useful when a card should start at a fixed share of the row rather than a fixed pixel width, though the share is calculated before gaps are removed, so three 30% cards plus their gaps can still overflow a row.

Check: In a row container, does flex-basis behave more like a width or a height?

Section 23 — The flex shorthand

flex combines grow, shrink, and basis in one declaration: flex: 1 1 200px means grow 1, shrink 1, basis 200 pixels. You will see it written far more often than the three longhand properties, because it keeps the whole sizing idea in one place and makes the intention readable at a glance. When you read someone else's stylesheet, flex is the declaration to look for when you want to know how an item sizes itself; when you write your own, reaching for the shorthand first keeps sizing decisions together instead of scattered across three lines that a later editor might change independently.

The shorthand has rules about omitted values that are worth learning, because they cause real confusion. With one value, like flex: 1, the value is the grow factor, shrink defaults to 1, and basis defaults to 0% — not auto, which is the trap. With two values, like flex: 1 200px, the first is grow and the second is basis, with shrink defaulting to 1. With three values, the order is always grow, shrink, basis. And if you write a single value that is a width, like flex: 200px, it is treated as the basis, with grow 0 and shrink 1. These rules reward a moment of care: writing flex: 1 when you meant "grow from my natural size" gives you "grow from zero", which produces equal widths rather than content-proportioned ones. Both are legitimate outcomes, but they are different outcomes, and confusing them is a common source of "why are my columns equal when I wanted them proportional?" debugging sessions.

There are also three keyword forms of the shorthand that you will meet constantly in real code. flex: none means grow 0, shrink 0, basis auto — stay at your natural size, do not flex at all. flex: auto means grow 1, shrink 1, basis auto — flex, but start from your natural size. flex: initial means grow 0, shrink 1, basis auto — the default behaviour, stated explicitly. The difference between flex: 1 and flex: auto is the single most useful shorthand distinction in this chapter: flex: 1 starts from zero and produces equal final sizes, while flex: auto starts from content size and produces proportional final sizes. Section 45 returns to this pair, but keep the contrast in mind from now on, because it explains many layouts that otherwise look inconsistent.

When should you use the longhand properties instead of the shorthand? When you need to change only one part of the sizing without disturbing the others — for example, overriding just the grow value of an item in a media query, or setting a basis on a whole group while letting individual items keep their own grow values. The longhand properties are also clearer when you are teaching or debugging, because each line says one thing. But for the ordinary case of describing an item's complete sizing, the shorthand is the standard, and using it consistently makes stylesheets shorter and easier to scan. Whichever you choose, be consistent within a project, and remember the order: grow, shrink, basis.

.card {
  flex: 1 1 200px;
}

Check: What do the three values in flex: 1 1 200px mean, in order?

Section 24 — flex: 1 for equal columns

flex: 1 is shorthand for flex: 1 1 0%: grow 1, shrink 1, and start from zero. Because every item starts from zero, all of the container's space is spare space, and the grow values share it out — equally, if every item has the same grow value. Give every column flex: 1 and they come out exactly equal, whatever their content. A column containing a single word and a column containing a paragraph end up the same width, because their starting sizes were both zero and their growth was equal. It is the quickest way to build equal-width columns, and it is used constantly for that reason.

The zero basis is the key, and it is worth contrasting with the alternative. If the same columns used flex: auto — grow 1, basis auto — they would start from their content sizes and then share only the leftover space equally, so the paragraph column would end up wider than the word column. Both layouts are "flexible columns", but they answer different design questions: "make these equal" versus "let content set the proportions, then share the rest". Beginners often write flex: 1 expecting the second behaviour and are surprised by the first, or write flex: auto expecting equal columns and are surprised by the second. Naming the question before choosing the value prevents the surprise: equal, or proportional? Equal means flex: 1; proportional means flex: auto.

Equal columns have a wide range of uses beyond the obvious multi-column layout. A row of pricing plans that must look balanced regardless of feature-list length; a set of tabs that should divide a bar evenly; a footer with four link groups of equal width; a dashboard with three statistic panels that should match even when one number is much longer than the others. In each case, flex: 1 on every item delivers the equality in one declaration per item, and it keeps delivering it as content changes, because the equality comes from the shared zero basis rather than from any fixed width. If a panel's content grows, the panel does not grow with it — the content wraps or scrolls within the equal width, which is usually the desired behaviour for a balanced design.

There is one interaction to watch: the content minimum size still applies, as it does everywhere in Flexbox. An item with flex: 1 will not shrink below its content floor, so a column containing a very long unbroken word or a wide image may end up wider than its siblings, breaking the equality. The fix is the same as elsewhere — allow the content to wrap, set a minimum size of zero where appropriate (Section 40), or constrain the image — and the equality returns. Equality in Flexbox is a goal the browser pursues within the constraints of readable content, not an absolute guarantee, and understanding that balance is part of using the tool well. When equality matters more than content, Grid's equal tracks, covered in Chapter 10, can enforce it more strictly; when flexibility matters more, flex: 1 is the natural choice.

.column {
  flex: 1;
}

Check: Why does flex: 1 on every item produce equal widths?

Section 25 — order`order` changes the visual order of a single item. Items default to `order: 0`, and they are laid out from lowest to highest, so `order: -1` pulls an item to the front and `order: 2` pushes it back. Items with the same order value keep their HTML order relative to each other, so `order` only rearranges items whose values differ. It is a small property with an outsized ability to confuse, because it changes what the eye sees without changing anything else: the HTML order, the reading order, the tab order, and the order in which search engines encounter the content all stay exactly as written. The visual order becomes a layer on top of the document order, and the two can disagree.

The danger is using order casually, as a shortcut for rearranging a layout that could simply have its HTML reordered. When the visual order and the document order disagree without a reason, keyboard users and screen reader users experience a different page from sighted users: they tab to a button that visually appears last before they reach the content that visually appears first, or they hear a list in an order that contradicts the numbers on screen. That is not a theoretical concern; it is one of the most common accessibility failures in flex layouts, and it is covered again in Section 48. A good rule is to ask, before using order, whether you could achieve the same result by moving the element in the HTML. If you can, move it in the HTML. If you cannot — because the document order carries meaning that the visual order should not override — then order is the right tool, and a comment explaining why will help the next developer understand the choice.

There are also practical details worth knowing. order works within a single flex line or, in a wrapping container, within the line the item lands on; it does not move items between lines. Negative values are allowed and are the usual way to pull one item to the front without renumbering everything else. And order interacts with the reverse directions: in a reversed container, the lowest order still comes first in flex terms, but "first" is displayed at the opposite end, so the combination can be doubly confusing and is best avoided unless you have worked through the result carefully. Like all the reordering tools in this chapter, order is powerful precisely because it separates appearance from structure — and that separation demands care about which story each layer is telling.

.featured {
  order: -1;
}

Items that share an order value keep their document order relative to each other, so order only rearranges items whose values differ. That detail makes small, targeted reordering safe without renumbering a whole list.

Check: Does changing order change the tab order of a page?

Section 26 — align-self`align-self` lets one item override the container's `align-items` just for itself. If a row centres everything but one card should stick to the top, give that card `align-self: flex-start`. It takes the same values as `align-items` — `auto`, `flex-start`, `flex-end`, `center`, `baseline`, and `stretch` — and it only affects that one item's cross-axis position, leaving its siblings exactly where they were. The default value is `auto`, which means "follow the container's align-items", so an item without an explicit align-self simply inherits the group's alignment, and setting align-self is how you carve out an exception.

It is important to be clear about what align-self does not do. It does not move an item along the main axis — that is the job of justification and auto margins. It does not change an item's size, except in the sense that stretch will grow it to fill the cross size while other values will let it keep its natural cross size. And it does not affect the item's position within a wrapped line's stack — that is align-content's territory. Keeping these boundaries straight prevents a common debugging loop in which a developer tries every alignment property on an item in turn, hoping one of them will move it horizontally, when the property they need is a main-axis one on the container or an auto margin on the item. Ask which axis you mean, and whether you mean the group or the individual, and the property chooses itself.

A related detail is the interaction between align-self: stretch and an item with a fixed cross size. Stretch cannot override an explicit height in a row or an explicit width in a column, so an item with a set height and align-self: stretch will keep its height and behave as if its alignment were something else — usually appearing at the start of the cross axis. If a stretched item refuses to stretch, check for a fixed cross size before assuming the alignment is broken. Conversely, an item with align-self: center in a tall container will keep its natural height and sit in the middle, which is the usual way to stop a small badge or button from stretching to the full height of a bar. The pair — container sets the rule, item sets the exception — is one of the most useful patterns in Flexbox, and it is worth practising until it feels automatic.

.tall-card {
  align-self: flex-start;
}

Another common use is in a card row where one card holds a short alert that should stay at the top while its neighbours stretch. Giving that card align-self: flex-start keeps it compact, and the row still looks deliberate, because the exception reads as a design choice rather than an accident.

Check: What is the difference between align-items and align-self?

Section 27 — Margins with auto in Flexbox

In a flex container, margin-left: auto on an item pushes it as far right as possible, absorbing all the spare space on that side. This is a neat trick for a navigation bar where most links sit on the left and one link, such as "Log in", sits on the right — no extra wrapper element needed, and no need to split the navigation into two groups. The mechanism is simple: an auto margin in a flex container absorbs free space, just as auto margins centre a block in ordinary layout, but here the absorption happens on one side only, pushing the item to the opposite side. Set margin-left: auto and the item goes right; set margin-right: auto and it goes left; set both and the item centres itself, with equal space absorbed on each side.

The auto-margin trick is more flexible than it first appears, because it can divide a row into groups. Give the first item of the right-hand group margin-left: auto, and everything before it stays left while everything from it onwards goes right. Give two items auto margins and the row splits into three groups with the middle group centred. This grouping is how many real headers are built: logo on the left, navigation in the middle, actions on the right, with two auto margins doing the work of what would otherwise require nested containers. It is also how a single item can be centred between two groups without knowing their widths — the auto margins absorb whatever space is available on each side, equally, regardless of how wide the groups are.

Auto margins also work on the cross axis, though this is used less often. In a row container, margin-top: auto on an item pushes it to the bottom of the container, absorbing the spare vertical space above it. Inside a column card, margin-top: auto on the button pushes the button to the bottom of the card, which is the technique behind the aligned card buttons in Section 30. The same principle — auto margin absorbs free space on its side — applies on both axes, and recognising that unity makes the trick easier to remember: auto margin means "take all the free space on this side", wherever that side is.

There are limits to be aware of. Auto margins only absorb free space, so if the container is full or overflowing, they have nothing to absorb and no visible effect. They also compete with justify-content: if justification is already distributing the free space, an auto margin will claim its share first, and justification will distribute what remains, which can produce surprising results if you expected one mechanism to win outright. In practice, most layouts use one or the other for a given axis — justification for the group's overall distribution, auto margins for pushing specific items to an edge — and mixing them on the same axis is a sign to simplify. Used deliberately, though, auto margins are one of the most elegant tools in Flexbox, turning a grouping problem into a single declaration.

.login-link {
  margin-left: auto;
}

Check: What does margin-left: auto do to a flex item in a row?

Section 28 — Centring in both directions

To centre one item perfectly in a container, set the container to flex, then justify-content: center and align-items: center together. The first centres on the main axis, the second on the cross axis, and the combination centres in both directions at once. This replaced a whole bag of old centring tricks — absolute positioning with negative margins, table-cell display hacks, line-height matching — each of which worked only in specific conditions and broke when content changed size. The flex version works for any content size, any container size, and any direction, and it keeps working when the content changes, which is why it became the standard answer to the most asked layout question on the web.

The pattern needs one thing to be visible: the container must be larger than the item in both directions. A container that wraps snugly around its content has no spare space to centre within, and the item will appear uncentred simply because there is nowhere for it to move. For a hero section, that usually means giving the container a height — min-height: 100vh for a full-screen hero, or a fixed height for a banner — and for a modal or empty state, it means the container fills its parent. Once the container has room, the two declarations do the rest, and the item sits dead centre. If your centred item is not centred, check the container's size before doubting the properties.

Centring a group works the same way: the group as a whole is centred on the main axis by justification, and each item is centred on the cross axis by alignment. A row of buttons centred in a hero, a set of icons centred in a panel, or a login form centred on a page all use the same two declarations, with gap added for spacing within the group and flex-direction: column added when the group should stack vertically and still centre. The column variant is worth noting, because it swaps the axes: in a column container, justify-content: center centres vertically and align-items: center centres horizontally, but the pair still produces dead-centre placement. The declarations do not change; only the axes they refer to have swapped, which is the axis model from Section 4 paying off again.

There is a subtlety with centring in a container that can scroll or overflow. If a centred item is taller than its container, centring it on the cross axis can push its top edge above the container's top, where it may be unreachable by scrolling in some browsers — the content is centred, but the overflow is cut off at the top rather than the bottom. The modern remedy is to use margin: auto on the item instead, or to ensure the container can grow, but for ordinary hero and panel layouts where the content fits, the two-declaration pattern is safe and standard. It is also worth remembering that centring is a design choice, not a default: centred layouts suit heroes, empty states, and dialogs, but centred body text and centred navigation can harm readability and scanning, so use dead-centre placement where it serves the content, not everywhere by habit.

.hero {
  display: flex;
  justify-content: center;
  align-items: center;
}

Check: Which two properties together centre an item both horizontally and vertically?

Section 29 — Building a navigation barA navigation bar is the classic Flexbox job: a container with `display: flex`, `justify-content: space-between` for a logo and links at opposite ends, and `align-items: center` so everything lines up vertically. Add `gap` between the links so they breathe, and the bar is essentially done. That short description contains almost the whole recipe, and it is worth appreciating how little code a professional-looking navigation bar now requires compared with the float-based bars of the past, which needed clearfixes, explicit widths, and vertical-align hacks, and still broke when a link wrapped. The flex version adapts to different logo sizes, link counts, and font sizes without adjustment, which is why it is the first layout most developers build with Flexbox and the one they return to as a reference.

A real navigation bar usually has more structure than a single row of links. The common shape is a logo group on the left, a links group in the middle or on the right, and sometimes an actions group — a search box, a sign-in link, a call-to-action button — at the far right. You can build this in two ways. One way is a single flex row containing all the elements, with auto margins dividing the groups, as Section 27 describes. The other way is nested containers: the bar is a flex row holding a logo element and a nav element, and the nav element is itself a flex row holding the links with a gap. The nested version is usually clearer, because each level has one job: the bar positions its two groups, and the nav spaces its links. Clarity matters here, because navigation bars are edited often — links are added, labels change, a button is promoted — and a structure that is easy to edit is a structure that stays correct.

Responsive behaviour is the next consideration, and it should be planned from the start rather than bolted on later. On a wide screen, the bar is a row. On a narrow screen, the same bar may wrap, stack into a column, or collapse behind a menu button, depending on the design. The simplest responsive answer is a media query that switches the bar's direction to column at a breakpoint, stacking the logo above the links — it is not glamorous, but it keeps every link reachable without JavaScript, which is a genuine virtue. A more polished answer hides the links behind a button that toggles them with JavaScript, but that requires the button, the toggle logic, and accessibility handling for keyboard and screen reader users, and it should not be the first version you build. Start with the stacking version, confirm the navigation works at every width, and only then add the toggle as an enhancement.

.navbar {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

Check: Which two properties would you use for a logo on the left and links on the right, all vertically centred?

Section 30 — A row of equal-height cardsPut cards in a flex row and the default `align-items: stretch` makes them all as tall as the tallest card, which looks tidy even when the text lengths differ. That single default solves a problem that used to require JavaScript height-matching or elaborate table layouts: cards whose content varies, but whose borders should line up. The mechanism is the one from Section 12 — stretch grows each item to the container's cross size, which in a row is the height set by the tallest item — and it works without any declaration on the cards themselves, because stretch is the default alignment. You only need to notice that it is happening and design with it in mind, rather than fighting it by giving cards fixed heights.

Equal card heights alone are not quite enough for a polished result, because the buttons inside the cards will still sit at different heights if the text lengths differ: a card with short text has its button higher than a card with long text. The standard fix is to make each card itself a flex column, and give its button margin-top: auto. The column direction stacks the card's title, text, and button vertically; the auto top margin on the button absorbs all the spare vertical space inside the card, pushing the button to the bottom. Since the cards share a height, their bottoms line up, and so do their buttons. Two levels of Flexbox — a row for the cards, a column inside each card — combine into a layout that looks deliberately designed rather than accidentally uneven, and the technique generalises to any component where a footer element should sit at the bottom of a variable-height box.

The wrapping question matters here too. A row of cards that does not wrap will overflow on narrow screens, so a real card row uses flex-wrap: wrap with a basis on the cards, as Section 32 describes. When cards wrap, equal heights apply within each row, not across the whole grid: cards on one line share that line's height, and cards on the next line share the next line's height, which may differ. That is usually fine — the eye compares cards within a row, not across rows — but it is worth knowing, because it differs from Grid, where rows can be sized to match across the whole layout if you ask for it. If your design requires all cards on all rows to share one height, Grid is the better tool; if per-row equality is enough, Flexbox is simpler.

.cards {
  display: flex;
  gap: 16px;
}

.card {
  display: flex;
  flex-direction: column;
  flex: 1;
}

The card pattern also benefits from thinking about images. A card image at the top of a column card will stretch across the full card width, but its height depends on its own proportions unless you control it. Without control, cards that share a height can still look uneven because their images have different heights and push the text to different starting points.

Check: Why do cards in a flex row come out the same height by default?

Section 31 — Sidebar and contentA sidebar layout is a flex row with two items: a sidebar with a fixed basis, such as `flex: 0 0 220px`, and a main area with `flex: 1` that takes the rest. The `0 0` on the sidebar means "do not grow, do not shrink", so its width stays put while the main area flexes around it. The main area's `flex: 1` means "start from zero and grow to take everything available", which in a two-item row means everything the sidebar does not take. Together, the two declarations describe the whole layout: one fixed part, one flexible part, and the browser handles the arithmetic at every width. It is a pattern you will use for admin panels, documentation pages, dashboards, and any design with a navigation column beside a content column.

The sidebar's fixed size deserves a closer look, because there are several ways to write it and they are not identical. flex: 0 0 220px is the most explicit: grow 0, shrink 0, basis 220 pixels. An alternative is width: 220px with flex-shrink: 0, which achieves the same practical result through a different route — the width sets the starting size, and the shrink protection stops it from being squeezed. A third option is flex-basis: 220px alone, which leaves grow at 0 and shrink at the default 1, so the sidebar can shrink on narrow screens — sometimes desirable, sometimes not. The choice depends on whether the sidebar should ever give up width. For a navigation sidebar with icons and labels, the answer is usually no, and the 0 0 form states that plainly. For a sidebar that can gracefully narrow, leaving shrink on is the better choice.

Responsive behaviour for a sidebar layout usually means stacking on narrow screens: at a breakpoint, the row becomes a column, or the sidebar moves below the content, or it collapses into a toggle. The simplest version is a media query that switches the container's direction to column, so the sidebar sits above the content at full width — the sidebar's fixed basis then becomes a height concern, so you will usually also reset its flex value in the media query, for example to flex: none with width: auto, letting it size naturally as a horizontal strip. Planning that breakpoint when you first build the layout, rather than discovering the overflow later, is the difference between a layout that feels responsive and one that feels patched. Test the sidebar layout at a phone width early, and decide which stacking or collapsing behaviour your design wants.

.sidebar {
  flex: 0 0 220px;
}

.main {
  flex: 1;
}

A sidebar layout also needs a plan for very wide screens. When the main area grows without limit, lines of text become long and hard to read, even though the layout itself is technically correct. A maximum width on the main content, centred within the flexible area, keeps reading comfortable while the sidebar stays fixed at the edge.

Check: What does flex: 0 0 220px mean for the sidebar?

Section 32 — A wrapping card layoutCombine `flex-wrap: wrap`, `gap`, and a `flex-basis` on the cards, and you have a responsive card layout: cards sit side by side when there is room and wrap when there is not. Add `flex-grow: 1` and the cards on a partly filled last row stretch to fill it; leave it off and they keep their natural width. The standard recipe, `flex: 1 1 220px` on the cards, gives each card a starting width of 220 pixels, permission to grow into spare space, and permission to shrink when crowded, inside a container that wraps and gaps. On a wide screen, several cards share each row; on a narrow screen, fewer fit, and eventually each card takes the full row. No media query is needed for the wrapping itself, though you may add one for other changes, such as larger text or a different gap at small widths.

The choice of basis is the main design decision in this pattern. A small basis, like 160 pixels, packs many cards per row and suits dense galleries or icon grids. A large basis, like 320 pixels, gives fewer, roomier cards and suits product listings or article previews where text needs breathing room. The basis is not a promise — cards will grow beyond it when there is spare space and shrink below it when crowded — but it is the anchor around which the layout organises itself, and changing it is the quickest way to change the density of the whole layout. Try a range of bases in a browser with the same cards and watch the column count change: the relationship between basis, container width, and column count becomes intuitive very quickly, and that intuition is what lets you pick a basis confidently for a new design.

Gap interacts with wrapping in a way that is easy to get wrong by hand but easy to get right with Flexbox. The browser subtracts the gaps from the available space before deciding how many cards fit per row, so a row of 220-pixel cards with 16-pixel gaps in a 700-pixel container fits two cards per row with room to spare, not three, because three cards plus two gaps need 692 pixels plus the cards' growth — and the exact fit depends on grow and shrink as well. The point is not to calculate this by hand, but to trust the browser's calculation and to test at several widths. Resize slowly through the breakpoints where the column count changes, and watch for awkward widths where cards look stretched or cramped. Adjusting the basis by a few dozen pixels usually resolves those awkward zones, and the result is a gallery that feels considered at every width rather than only at the widths you happened to test.

.gallery {
  display: flex;
  flex-wrap: wrap;
  gap: 16px;
}

.gallery .card {
  flex: 1 1 220px;
}

Check: In a wrapping card row, what decides when a card drops to the next line?

Section 33 — Flex items and minimum sizesA flex item will not shrink below the size of its content by default — a long word or a wide image can force a row to overflow. That is usually the browser protecting your content from being squashed unreadably, and it is a deliberate part of the flex specification: the automatic minimum size of a flex item is the size of its smallest content that cannot be broken. For text, that means the longest word; for an image, its natural width; for a button, its label plus padding. The browser would rather overflow than damage content, and in most cases that preference is correct — squashed, unreadable content is worse than an overflow you can see and fix.

This protection explains several otherwise mysterious overflows. A navigation bar with a long site name that refuses to shrink; a card row where one card stays wide because it contains a wide chart image; a form row where an input with a long placeholder forces its siblings narrow. In each case, the item is not ignoring its shrink value — it has shrunk as far as its content allows, and the floor has stopped it. Recognising the floor changes the debugging approach completely: instead of increasing the shrink value, which cannot move the floor, you address the content — let the word break, constrain the image, shorten the label, allow wrapping, or change the layout at that width. The floor is information about the content's needs, and the fix is almost always about the content or the layout, not about the shrink number.

There is a distinction between the automatic minimum size and an explicit minimum size that is worth knowing. An explicit min-width on an item sets a floor you chose; the automatic minimum is a floor the browser inferred from content. Setting min-width: 0 removes the automatic floor, allowing the item to shrink below its content size, which is the technique Section 40 uses for truncation. But removing the floor has consequences: text may overflow its item, images may be squashed if they also have flexible sizing, and content can become unreadable. It should be done deliberately, on items where you have also handled the overflow — with truncation, wrapping, or scrolling — rather than as a blanket fix applied to every flex item in the hope that overflows disappear. A blanket min-width: 0 on everything is a common cargo-cult fix that hides the symptom while creating new problems.

/* A long unbroken word can keep this item wide */
.item {
  flex: 1;
}

The same floor applies to flex items in a column, where a wide image or a long word can force a column wider than intended, and the diagnostic steps are identical: find the item holding its size, find the content setting its floor, then decide whether the content, the item, or the layout should change.

Check: Why might a flex row overflow even though its items are allowed to shrink?

Section 34 — Nested flex containersA flex item can itself be a flex container, and this nesting is how most real layouts are built: a page row holds a sidebar and a main area, the main area is a column holding a header and cards, and each card is a column holding text and a button. Each level only worries about its own direct children, which keeps every level simple even when the whole page is complex. This is the key to scaling Flexbox beyond toy examples: you do not build a complicated layout in one container, you build several simple containers, each responsible for one arrangement, nested inside each other like boxes within boxes.

The nesting principle also explains why Flexbox layouts are easier to maintain than the float layouts they replaced. In a float layout, every element's position depended on the elements around it in complicated ways, and changing one element could disturb distant parts of the page. In a nested flex layout, changing a card's internal arrangement — moving its button, adding a badge, restacking its text — affects only that card, because the card's internal container manages its own children and the outer row only cares about the card's outer size. The boundaries between levels act as firebreaks: problems stay local, and fixes stay local too. When you find yourself trying to make one container do two jobs — arranging both the cards and the contents of the cards — that is a sign to add a level of nesting and let each container do one job well.

There are a few practical points that make nesting work smoothly. First, remember that a flex item's size is determined by the outer container, but its internal layout is determined by its own flex properties — the two do not interfere, because they operate on different sets of children. A card that is a flex item in a row can be a column container for its own children without any conflict. Second, watch the default stretch behaviour at each level: a card stretched to the row's height will pass that height to its internal column, which is what makes the pushed-to-the-bottom button technique work. Third, keep the nesting shallow where you can: three or four levels is normal for a page, but ten levels usually means the structure has become convoluted, and simplifying the HTML — removing unnecessary wrapper elements — will simplify the CSS as well. Every wrapper should earn its place by doing a layout job.

<div class="page">
  <aside>Sidebar</aside>
  <main class="cards">
    <article class="card">Card one</article>
    <article class="card">Card two</article>
  </main>
</div>

A useful test for nesting is to remove a level and see whether the layout still works. If it does, the level was unnecessary, and removing it simplifies the structure for the next reader without changing the result.

Check: Can a flex item also be a flex container for its own children?

Section 35 — Flex versus block layout

In normal block layout, elements stack vertically and take the full width available. A paragraph follows a heading, a div follows a div, each on its own line, each as wide as its parent. That behaviour is the default for most HTML elements, and it is perfectly suited to documents: articles, essays, and pages of text are naturally vertical stacks. Flex layout replaces that stacking with arrangement along an axis, chosen by the container, with control over spacing, alignment, growth, and wrapping. You are not choosing one layout for a whole page — you use block layout where stacking is right, and flex where a row or an aligned group is right, often nested together, with block elements inside flex items and flex containers inside block elements.

The differences go beyond direction. In block layout, vertical margins collapse: the margin between two stacked paragraphs is the larger of the two margins, not their sum, which is a rule that surprises beginners but makes document spacing feel natural. In flex layout, margins do not collapse, and spacing is handled by gap and justification instead, which makes the spacing predictable in a different way — what you set is what you get, with no collapsing to account for. In block layout, an element's height is set by its content, and centring it vertically requires extra work. In flex layout, the container can stretch, centre, or distribute its items with single declarations. Neither system is better in general; they are different tools, and the skill is recognising which kind of arrangement a given group of elements needs.

A useful way to decide is to ask what the group is. If the group is a sequence of things to be read top to bottom — paragraphs, sections, list items in an article — block layout is the natural choice, and adding Flexbox would only complicate it. If the group is a set of things to be arranged — navigation links, cards, buttons, a header's parts — flex layout is the natural choice, because arrangement is exactly what it controls. Many pages are mostly block layout with flex islands: an article page is a block stack containing a flex header, a flex toolbar, and a flex card row. That mixture is normal and healthy, and it reflects the document-plus-components nature of the modern web: documents at the top level, components inside them.

There is also a historical perspective that helps. Before Flexbox, developers forced block and float layout to do arrangement jobs it was never designed for, and the resulting techniques — float grids, clearfixes, inline-block grids with font-size hacks — were fragile precisely because they fought the layout model. Flexbox ended that era for one-dimensional arrangement, just as Grid ended it for two-dimensional arrangement. Learning to use each layout model for the job it was designed for, rather than forcing one model to do everything, is the deeper lesson of this section. When a layout feels like a fight, it is often a sign that you are using the wrong model for that group, and switching models — from block to flex, or from flex to grid — is usually easier than continuing to fight.

/* Block by default: stacked, full width. Flex: arranged along an axis. */

Check: In block layout, how do elements normally arrange themselves?

Section 36 — A page as a flex columnSet the page wrapper to `display: flex` with `flex-direction: column`, and the header, main content, and footer stack vertically — but now they are flex items, so you can make the main area grow to fill the viewport while the header and footer keep their natural height. This is the foundation of the sticky footer in the next section, and it is also a useful way to think about page structure in general: a page is a column of sections, and treating it as a flex column gives you control over how those sections share the viewport's height. The header and footer take what they need, the main area takes the rest, and the whole page behaves sensibly on both short and long content.

Once the scaffold is in place, the sections can be given their flex behaviour. The header and footer usually need no flex declarations at all: with grow defaulting to zero, they keep their natural heights, which is what you want for a header containing a navigation bar and a footer containing a few lines of links. The main area gets flex: 1, meaning it grows to absorb all the spare vertical space, pushing the footer to the bottom on short pages and simply taking its natural height on long pages, where there is no spare space to absorb. Nested containers then handle the internal layout of each section: the header is a flex row for its logo and links, the main area may contain flex rows of cards, and the footer may be a flex row of link groups. The page column is the outermost level of the nesting tree from Section 34, and every level below it follows the same pattern.

There is a subtlety about the main area's growth and its own content that is worth knowing. When the main area grows to fill spare space, its children do not automatically stretch with it — they keep their natural heights unless the main area is itself a flex container with appropriate alignment. If you want the main area's content to fill the grown height, for example a hero section that should fill the viewport, you make the main area a flex column too, and give its child the growth. The pattern recurses: each level distributes space to its own children, and no level distributes space to its grandchildren. That discipline — each container manages only its direct children — is what keeps nested flex layouts predictable, and the page column is the level where the discipline starts.

.page {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}

The page column pattern works best when the wrapper height is a minimum, not a fixed height. A fixed height would clip content on a long page or force scrolling inside the page itself, whereas a minimum height lets the page grow naturally when content exceeds the viewport. That is why the usual declaration uses a minimum rather than a plain height.

Check: What does min-height: 100vh do for a flex column page wrapper?

Section 37 — A sticky footer with FlexboxA sticky footer is one that sits at the bottom of the viewport on short pages but moves down naturally on long ones. It is called sticky, though it does not use `position: sticky` — the name predates that property and refers to the footer sticking to the bottom of the screen rather than floating halfway up a short page. Before Flexbox, a sticky footer required negative margins, wrapper padding tricks, or JavaScript height calculations, all of which were fragile. With Flexbox, it requires a page column, a minimum viewport height, and one grow declaration, and it works reliably. That contrast is a good measure of how much Flexbox simplified everyday layout.

The recipe has three parts, and all three are necessary. First, the page wrapper is a flex column, as in Section 36. Second, the wrapper has min-height: 100vh, so it is at least as tall as the viewport. Third, the main content area has flex: 1, so it grows to absorb any spare vertical space. On a short page, the spare space exists, the main area grows, and the footer is pushed to the bottom of the viewport. On a long page, there is no spare space — the content already exceeds the viewport — so the main area takes its natural height and the footer sits below the content, reachable by scrolling, exactly as it should. The footer is never fixed in place, never overlaps content, and never requires the content to know the footer's height, which is why this pattern replaced the older fixed-footer and negative-margin approaches.

It is worth distinguishing this pattern from a fixed footer, which stays visible on screen while the page scrolls beneath it, using position: fixed — a technique covered in Chapter 11. A fixed footer is always on screen, which suits a music player bar or a mobile action bar, but it also always covers part of the content, so the content needs bottom padding to stay readable. A sticky footer, in the Flexbox sense, is not always on screen: on a long page it scrolls away like any other content, and it only appears at the bottom of the viewport when the page is short. The two solve different problems, and confusing their names leads to choosing the wrong one. If the requirement is "the footer should not float in mid-page on short pages", the flex pattern is the answer. If the requirement is "the footer should always be visible", fixed positioning is the answer.

.page {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}

.content {
  flex: 1;
}

One failure mode to watch for is a page where the main area contains very little content but a large element, such as a hero image, that is taller than expected. In that case the footer still sits below the content, which is correct, but the page may scroll slightly even though it looks short. That is not a fault in the footer pattern.

Check: In a flex column page, which part gets flex: 1 to push the footer down?

Section 38 — align-content for multiple lines

When a flex container wraps into several lines, justify-content still works per line, but align-content controls how the lines themselves are spread along the cross axis — packed together, spaced out, or centred as a group. With only one line, it has nothing to do, which is why it sometimes seems to have no effect: a single line is not a group of lines, and there is no inter-line space to distribute. The values mirror the justify family — flex-start, flex-end, center, space-between, space-around, space-evenly, and stretch — but they apply to lines rather than items, and that distinction is the whole point of the property. If you remember one sentence about align-content, make it this: justify spreads items within a line, align-content spreads lines within the container.

The stretch default deserves attention, because it produces a behaviour that surprises people in wrapping layouts. With align-content: stretch, the lines themselves stretch to fill the container's cross size, so each line gets an equal share of the height, and items within a line stretch to their line's height. In a wrapping card gallery inside a tall container, that can make the rows taller than their content, with cards stretched to fill rows that have been stretched to fill the container — a double stretching that looks odd if you expected the rows to hug their content. The remedy is usually align-content: flex-start, which packs the lines at the cross start and lets each line be as tall as its own content. If your wrapped rows look unexpectedly tall, align-content is the first property to check, because it is the one most developers forget exists.

The spacing values have their own uses. space-between on the lines spreads the first line to the top and the last line to the bottom, with even space between lines — a layout that suits a dashboard of panels filling a fixed-height area, or a gallery that should fill a tall container evenly. center gathers the lines as a group in the middle of the container, which suits a small set of wrapped items inside a large panel, such as a group of tags centred in a hero. space-around and space-evenly give the lines breathing room with the same end-gap differences as their justify counterparts. In each case, the container needs spare cross space for the effect to be visible, just as justification needs spare main-axis space — a tall container with few lines, or a container with a fixed height, is where align-content shows its value.

A practical way to learn align-content is to build a wrapping container with a fixed height — say 600 pixels — containing six small boxes that wrap into three lines, and then cycle through the align-content values, watching the lines move as a group. Compare that with cycling through align-items on the same container, and watch the boxes move within their lines instead. The contrast makes the two properties' roles unmistakable: align-items moves items within lines, align-content moves lines within the container. Once you have seen both movements, you will never confuse them again, and you will know which one to reach for when a wrapped layout's vertical rhythm looks wrong.

.wrapped {
  display: flex;
  flex-wrap: wrap;
  align-content: space-between;
}

Check: When does align-content have a visible effect?

Section 39 — flex-basis versus width`flex-basis` and `width` look similar in a row, but they are not the same, and the difference matters most when the two disagree. `flex-basis` is the starting size for flex maths — growing and shrinking work from it, and wrapping decisions are based on it. `width` is a final size in ordinary layout, and in a flex item it influences the starting size only when no basis is set. Once a basis is set, the basis wins for flex purposes, and `width` is effectively ignored as a sizing input, though it can still affect other things, such as the size of the item's content box in some edge cases. The practical rule from Section 22 bears repeating: inside a flex container, set `flex-basis` when you mean "start here, then flex", and reserve `width` for cases where you want a fixed box regardless of flex behaviour.

The difference becomes visible in a few specific situations. One is the flex shorthand's default basis: flex: 1 sets basis to zero, ignoring any width you may have set on the item, which is why a column with width: 300px and flex: 1 does not stay 300 pixels wide — the shorthand's zero basis overrode the width as the flex starting point. If you wanted the width respected, flex: auto would have been the right choice, because its basis is auto, which falls back to the width. Another situation is box-sizing: basis and width both refer to the content box by default, or to the border box if you have set box-sizing: border-box, which most modern projects do, so padding and borders are included in the stated size. Consistent box-sizing removes a whole class of sizing surprises, and it is worth setting globally at the start of any project.

When should you use width inside a flex item at all? When the size should not participate in flex behaviour: a fixed-size icon that must stay exactly 24 pixels regardless of container space is better served by a width plus flex: none than by a basis that might be overridden by a shorthand elsewhere. Or when you are setting the size of something inside the item, rather than the item itself — the width of an image within a card, for example, is an ordinary width, not a flex basis, because the image is not a flex item unless its parent is a flex container. The guideline is about roles: flex properties describe an item's participation in its container's layout, and ordinary sizing properties describe the box itself. Most of the time, for flex items, the flex description is the one you want.

.card {
  flex-basis: 240px;
}

Another place where the difference shows up is in responsive design. A basis that works on a wide screen may be too large on a narrow screen, so the basis itself often needs to change in a media query. Width, by contrast, is sometimes left unchanged because it describes the box rather than its flex participation.

Check: If you want a card to start at 240px but still grow and shrink, which property should you set?

Section 40 — Letting text truncate inside a flex itemBecause flex items resist shrinking below their content size, a long line of text inside one can overflow. Setting `min-width: 0` on the item removes that floor and lets it shrink, and then `overflow: hidden` with `text-overflow: ellipsis` can trim the text with dots. The three declarations work together: `min-width: 0` permits the shrinking, `overflow: hidden` clips the excess, and `text-overflow: ellipsis` replaces the clipped end with an ellipsis character, signalling to the reader that text has been cut off. `white-space: nowrap` is usually added as well, to keep the text on one line, since ellipsis truncation is a single-line technique — multi-line truncation requires different, newer properties and is a separate topic.

The deeper question is whether truncation is what you actually want. Truncation hides information, and hidden information is information the user cannot act on. A truncated product name, a truncated email address, or a truncated error message can mislead or frustrate, because the user cannot see the full text and may not know how to reveal it. Truncation is appropriate for secondary text where the full value is available elsewhere — a file name in a list where the full name appears on hover or on a detail page, a preview line that links to the full article, a username in a compact header where the profile page shows the full name. It is not appropriate for primary content, for text the user must read to complete a task, or for anything where the hidden part could change the meaning. When in doubt, let the text wrap instead of truncating it: wrapping preserves the information at the cost of height, while truncation preserves the height at the cost of information, and information is usually the more valuable of the two.

There are also accessibility considerations. Screen readers generally read the full text from the document, not the truncated visual display, which is good — the information is not lost to assistive technology. But sighted keyboard users and users with low vision may not be able to access the full text if the only way to reveal it is hovering with a mouse, which is why a truncated item should ideally also offer its full text through a title attribute, an expanded view, or a link to the full content. And the ellipsis itself should be understood as a signal, not a decoration: it tells the reader "there is more", and a design that truncates without signalling, or signals without providing the more, is breaking a promise to the reader. Use truncation sparingly, package it correctly, and always provide a path to the full text.

.truncate {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

Check: What does min-width: 0 allow a flex item to do?

Section 41 — Responsive Flexbox with a media queryFlexbox handles a lot of responsiveness by itself through wrapping, but sometimes you want a real change at a breakpoint: a row of links becoming a column on a narrow screen. A media query that switches `flex-direction` from row to column at, say, 600 pixels does that in two lines, and it is the simplest responsive pattern in Flexbox. The media query watches the viewport width, and when it crosses the threshold, the container's direction changes, and every property that depends on the axes follows: justification becomes vertical, alignment becomes horizontal, and the row becomes a stack. Nothing else needs to change, because the flex model adapts to the new direction automatically.

Within a media query, you can change any flex property, not just direction. A common set of small-screen changes includes switching a row to a column, reducing the gap, changing justification from space-between to flex-start so a stacked layout does not spread oddly, and resetting a sidebar's fixed basis so it becomes a full-width strip. The principle is to change as little as possible: the base styles describe the layout at its most natural width, usually the wide or the narrow version depending on whether you write mobile-first or desktop-first, and the media queries adjust only what needs adjusting. Mobile-first — base styles for narrow screens, media queries that add complexity as width grows — is the more common modern approach, because it keeps the base stylesheet simple and ensures the small-screen experience is designed rather than squeezed. Either approach works if applied consistently; mixing them carelessly produces media queries that fight each other.

It is also worth knowing what media queries cannot do, or should not be asked to do. They respond to the viewport, not to the container — a card component that appears in both a wide main area and a narrow sidebar cannot restyle itself based on its own width using a viewport media query, because the viewport is the same in both cases. Container queries, a newer CSS feature, address that gap, but they are beyond this chapter's scope, and for most layouts, viewport queries plus wrapping are enough. The honest summary is that Flexbox plus a handful of well-chosen media queries covers the responsive needs of the great majority of components, and the components that need more are usually the ones where Grid or container queries become the right next step. Build the flex layout, test it across widths, add a media query where it breaks, and repeat — that loop is responsive design in practice.

.menu {
  display: flex;
}

@media (max-width: 600px) {
  .menu {
    flex-direction: column;
  }
}

A useful discipline for media queries is to keep the breakpoint list short and meaningful. Each breakpoint should correspond to a width where the layout actually breaks, not to a device name or a round number chosen in advance. If a menu fits comfortably until a certain width and then crowds, that width is the breakpoint, whether it is a tidy number or not.

Check: How would you turn a horizontal menu into a vertical one on small screens?

Section 42 — Flexbox and images

Images inside flex items keep their natural size unless you tell them otherwise, and a wide image can force its item wider than you planned. An image's natural width is part of its content size, and the content minimum size from Section 33 means the item will not shrink below that width by default. A 1200-pixel photograph inside a 220-pixel card will therefore blow the card out to 1200 pixels, or overflow the row, unless you constrain it. The standard constraint is max-width: 100% on images inside flex layouts, which says "never wider than your containing item", and it is worth setting as a habit — many projects set it globally for all images, so that no image can ever overflow its parent, flex or otherwise.

Beyond constraining width, images in flex layouts often need height control as well. A card gallery looks best when its images share a shape, and photographs rarely do by nature. object-fit: cover combined with a fixed height or an aspect ratio lets an image fill a frame of a chosen shape, cropping its edges rather than distorting it, so a row of cards can show landscape, portrait, and square photographs in identical frames. The alternative, object-fit: contain, fits the whole image inside the frame with letterboxing, which preserves the image but produces uneven-looking frames. Cover is the usual choice for galleries and cards, contain for product images where seeing the whole item matters. Either way, the frame's shape is a design decision made in CSS, and the image adapts to it, rather than the image's shape dictating the layout.

There is also the question of what happens to an image's flex behaviour when the image itself is a flex item — for example, an icon beside text in a toolbar, or a thumbnail beside a title in a list item. An image as a direct flex item will be sized by its natural dimensions and by the flex rules, and it will stretch on the cross axis by default, which can distort it if the container is taller than the image. The fix is usually align-self: center or flex: none on the image, telling it to keep its natural size and sit centred, rather than stretching. Distorted images are one of the most visible flex mistakes, because the eye notices a stretched photograph instantly, and the fix is a single declaration once you know to look for the default stretch.

Finally, remember that images have loading and layout implications beyond their size. An image without width and height attributes causes layout shift as it loads, because the browser does not know how much space to reserve for it, and the surrounding flex items jump when the image arrives. Setting width and height attributes, or an aspect ratio in CSS, reserves the space in advance and prevents the jump, which matters for perceived performance and for users who are reading content that suddenly moves. It also helps the flex calculations, because the browser knows the image's shape before it loads. Good image handling in flex layouts is therefore a combination of constraints, fitting, alignment, and reserved space — four small decisions that together keep images behaving as good flex citizens.

.card img {
  max-width: 100%;
}

Check: Why might an image make a flex item wider than expected?

Section 43 — Button groups and toolbarsA toolbar is a row of buttons and controls where `display: flex` and `gap` do almost all the work: the buttons sit side by side with even spacing, `align-items: center` lines up icons with text, and a `margin-left: auto` can push a final control to the far end. It stays tidy as buttons are added or removed, because the spacing comes from the container's gap rather than from margins on individual buttons, and the alignment comes from the container rather than from adjustments on each control. That robustness matters for toolbars, which change often — features are added, buttons are hidden for permission reasons, icons are swapped — and a toolbar that needs manual spacing fixes after every change will gradually drift into inconsistency.

A toolbar's controls are rarely uniform, and Flexbox handles that variety well. A toolbar might contain text buttons, icon-only buttons, a search input that should grow, a divider, and a dropdown. The text buttons size to their labels; the icon buttons get flex: none so they never shrink; the search input gets flex: 1 so it absorbs spare space; the divider is a thin element with a fixed size; and the whole group is centred vertically by the container. Each control's flex behaviour is chosen for its role, and the container's gap keeps the spacing even across the variety. Trying to achieve the same result with margins would require a margin rule for each control type and special cases for the first and last, while the flex version needs one gap and one rule per control type for sizing — a much smaller surface for errors.

Accessibility for toolbars has its own conventions. A group of related buttons should be marked as a group — a div with a role="toolbar" and a label, or a set of buttons in a nav where appropriate — so assistive technology can announce the group's purpose. Icon-only buttons need accessible names, through text that is visually hidden or an aria-label, because an icon alone conveys nothing to a screen reader. Keyboard users should be able to reach every control in a sensible order, which means keeping the HTML order and the visual order the same, as Section 48 stresses. A toolbar is a control panel for your application, and building it with Flexbox gives you the layout for free — but the layout is only half the job, and the other half is making sure every control can be found, understood, and operated by every user.

.toolbar {
  display: flex;
  gap: 8px;
  align-items: center;
}

One more flex item to watch is a select element or a long button label, which can set a surprisingly large content floor and squeeze its neighbours. Constraining its width or allowing its text to wrap usually restores the balance.

Check: What two flex features give a toolbar its even spacing and vertical alignment?

Section 44 — Form rows with FlexboxA common form pattern is a label beside its input, with the input taking the remaining width. A flex row with the label at its natural size and the input at `flex: 1` achieves it, and it adapts to different label lengths without a fixed-width label column. The label sizes to its text, the input grows to fill whatever width remains, and the row as a whole fills its parent, so a form of such rows looks aligned and professional without any column-width calculations. If a label is longer than usual, the input simply becomes a little narrower, and the form still lines up, because the alignment comes from the row structure rather than from a shared fixed width.

That adaptability is the advantage over the older fixed-label-column approach, but it has a trade-off worth knowing. With natural-size labels, the inputs in different rows will not line up with each other unless the labels happen to be the same length — a short label leaves a wider input than a long label does, so the input edges form a ragged line down the form. Some designs accept that raggedness as the price of flexibility, especially in short forms where each row is read independently. Other designs prefer aligned inputs, and for those, a fixed label width — either a flex-basis on the labels or a Grid layout with a label column — is the better choice. The decision is a design judgement about what matters more: adaptability to label length, or a strict vertical line of input edges. Knowing that Flexbox's natural version gives you the first and Grid gives you the second helps you choose the right tool for each form.

Forms also raise the question of small screens, where a label beside its input may become too cramped. The standard responsive answer is a media query that switches the form rows from row to column, stacking each label above its input at narrow widths. The switch is a single direction change, as in Section 41, and it transforms the form from a two-column arrangement into a single-column one without restructuring the HTML. Combined with full-width inputs in the stacked version — the input's flex: 1 in a column makes it fill the width — the result is a form that reads comfortably at every size. Test every form at a phone width, because a form that is cramped on mobile is a form that users abandon, and the direction switch is a cheap fix for a costly problem.

.field {
  display: flex;
  gap: 12px;
  align-items: center;
}

.field input {
  flex: 1;
}

Form rows also need attention to vertical alignment when labels wrap onto two lines. A label that wraps will make its row taller, and the input beside it will sit in the middle of that taller row if the row uses centred alignment, which can look odd beside single line rows. Aligning the row to the start, or giving the input a top alignment that matches the first line of the label, often looks more natural.

Check: In a label-and-input row, which element gets flex: 1?

Section 45 — flex: none and flex: auto

Two handy shorthands complete the sizing vocabulary: flex: none means "do not grow, do not shrink, stay at your natural size", and flex: auto means "grow and shrink from your natural size". The first is shorthand for flex: 0 0 auto; the second for flex: 1 1 auto. They differ from flex: 1 in their starting point: flex: 1 starts from zero, while both of these start from the item's natural size — its content, or its width if one is set. That starting-point difference is the whole story of this section, and it is the distinction that explains why two layouts that both "use flex" can look so different.

flex: none is for elements that must not flex at all: icons, badges, logos, fixed-size buttons, dividers, and any element whose size is part of its meaning. An icon that shrinks becomes unrecognisable; a badge that grows becomes a banner; a divider that stretches becomes a bar. Giving such elements flex: none protects them from both directions of flex behaviour, and it states the intention plainly: this element's size is not negotiable. It is the flex equivalent of saying "leave this one alone", and it is the right default for any element whose size you have deliberately chosen, rather than leaving to the container's calculations. When a fixed element is being squashed in a crowded row, flex: none is usually the first fix to try, before adjusting the other items.

flex: auto is for elements that should flex but proportionally to their content. A row of navigation links where longer labels deserve wider buttons; a set of tags where the text length should set the relative widths; a toolbar where the search input should grow from its natural size rather than from zero. In each case, flex: auto starts from the content size and shares only the leftover space equally, so the original proportions are preserved and the growth is a bonus on top, rather than a replacement of the starting sizes. Compare that with flex: 1, which erases the starting sizes and produces equality. The two are not interchangeable, and the choice between them is the choice between "equal" and "proportional" from Section 24, restated as a shorthand decision.

A summary table in words may help fix the four shorthands. flex: initial is the default: no growth, shrink allowed, start from natural size. flex: none is stricter: no growth, no shrinking, start from natural size. flex: auto is generous: growth and shrinking, start from natural size. flex: 1 is equalising: growth and shrinking, start from zero. Most layouts need only two of these — flex: none for fixed elements and flex: 1 for equal or filling elements — with flex: auto for the proportional cases in between. Learning to reach for the right one by asking "should this start from zero or from its content?" will make your sizing declarations both shorter and more correct, because the shorthand carries the whole sizing story in a single, readable value.

.icon {
  flex: none;
}

Check: What is the difference between flex: none and flex: auto?

Section 46 — When not to use FlexboxFlexbox lays out in one direction at a time, and that single-direction focus is its limit as well as its strength. If you need a real two-dimensional layout — rows and columns that must line up with each other, like a page grid, a calendar, a photo wall with aligned rows, or a dashboard where panels in different rows must share column widths — CSS Grid is the better tool. Grid lets you define both dimensions and place items into the resulting cells, so alignment in one dimension does not depend on the contents of the other. Trying to build such a layout with Flexbox means fighting to keep rows aligned by hand: setting matching widths on items in different rows, hoping their content does not disturb the alignment, and adding media queries to re-align at every width. The fight is the signal that you have chosen the wrong tool.

There are other situations where Flexbox is not the best answer, even within one dimension. A long document of text — an article, an essay, a set of paragraphs — is best left to block layout, because its structure is a vertical sequence and Flexbox would add complexity without benefit. A table of data belongs in a table, not in a flex row, because tables carry semantics — rows, columns, headers — that assistive technology uses to make the data understandable, and a flex imitation of a table loses those semantics. A layout that needs items to overlap, such as a badge on an image or a modal over a page, needs positioning, covered in Chapter 11, because Flexbox arranges items without overlap by design. In each case, the question is not "can Flexbox do this?" — with enough effort, it usually can — but "is Flexbox the tool designed for this?" and the honest answer points elsewhere.

It is also worth recognising when Flexbox is being used for a single item that does not need it. A container with one child that simply needs centring is a fine use of Flexbox — the dead-centre pattern of Section 28 is standard — but a container with one child that needs no arrangement at all is just a wrapper, and adding display: flex to it adds nothing but a layer of abstraction for the next reader to puzzle over. Similarly, a row of two items that could be arranged with a simple margin or with Grid's two columns may be clearer in Grid if the two items must align with items in other rows. The goal is not to use Flexbox everywhere, but to use it where its model — a container distributing space along an axis — matches the problem. A developer who reaches for Flexbox for every layout, including two-dimensional ones, will write more code and get worse results than one who switches tools when the problem changes shape.

/* A page grid with aligned rows and columns: reach for Grid instead. */

Check: What kind of layout should make you reach for Grid instead of Flexbox?

Section 47 — Debugging Flexbox in developer toolsBrowser developer tools can show flex containers with an overlay: the container gets a badge, and inspecting it reveals the flex lines, gaps, and item sizes. When a layout looks wrong, inspect the container first and check which axis is which — most Flexbox confusion is a mix-up between the main and cross axes, and seeing the axes drawn on the page resolves it faster than re-reading the stylesheet. The overlay typically draws the main axis, marks the gaps, and outlines each item, turning an abstract model into a visible diagram. Learning to turn that overlay on — usually by clicking the flex badge next to the container in the inspector's element view — is a skill worth acquiring early, because it converts debugging from guesswork into observation.

A systematic debugging routine helps more than any single trick. First, confirm the container: is display: flex actually applied, and to the element you think? Stylesheets can be overridden, selectors can miss, and a declaration on the wrong element is the most common cause of "Flexbox does nothing". Second, confirm the direction: is the main axis where you think it is, or has a direction declaration or a media query changed it? Third, check for spare space: do the alignment and justification properties have room to act, or is the container exactly full? Fourth, check the items' sizing: what are their bases, grow and shrink values, and content minimum sizes, and is one item's floor holding the row open? Fifth, check for constraints: a width, height, min or max size, or margin that is quietly overriding the flex behaviour. Working through those five checks in order resolves the great majority of flex problems, and each check corresponds to a section of this chapter, which is why the chapter is organised the way it is.

Finally, remember that developer tools show you the layout, not the intention. They can tell you that an item is 300 pixels wide, but not whether you wanted it to be 300 pixels wide — that judgement is yours, based on the design. Debugging is therefore a conversation between the tools and the design: the tools report what is happening, the design says what should be happening, and the gap between them is the bug. Keeping a reference design or a sketch beside the browser while you debug makes that conversation concrete, and it prevents the common trap of adjusting values until the layout "looks about right" without knowing why. A layout that is right for a reason stays right when content changes; a layout that is right by accident breaks at the next content change. Use the tools to find the reason, fix the reason, and the layout will stay fixed.

/* If items are not where you expect, check: direction? wrap? axis? */

Check: What is the most common cause of a Flexbox layout looking wrong?

Section 48 — Visual order is not reading order`order` and the reverse directions change what sighted users see, but screen readers and keyboard users still move through the HTML order. If a visually reordered layout puts a "Buy" button before its product description in the code, keyboard users meet the button first, and screen reader users hear the button before the description that explains what they would be buying. That mismatch is confusing, and in the worst cases it makes a page genuinely harder or even impossible to use correctly — a user might activate a control before understanding its context, or miss content entirely because it appears in an unexpected place in the reading sequence. The visual order is a presentation layer; the reading order is the document's meaning, and the two should agree unless there is a deliberate, justifiable reason for them to differ.

The principle behind this is that HTML order carries meaning. The order of elements in a document is the order of the content's logic: title, then description, then action; question, then answer; heading, then the section it introduces. Assistive technology relies on that logic, because it cannot see the visual arrangement — it linearises the page into a sequence, and that sequence is the HTML order. Keyboard navigation relies on it too, because the tab order follows the document order by default. When CSS reorders the visual display without reordering the document, it creates two different pages: the one sighted users see, and the one everyone else experiences. That duality is sometimes necessary, as Sections 7 and 25 explain, but it is always a cost, and it should be paid only when the benefit is real — for example, when the document order must put the main content first for screen readers, but the design puts a sidebar first visually.

Testing for order mismatches is straightforward and should be part of your routine for any layout that uses reordering. Tab through the page with the keyboard, in the order a keyboard user would, and ask whether the focus moves in a sequence that makes sense visually — does it jump from the bottom of the page to the top, or from a button to an unrelated section? Then, if you can, run a screen reader over the page and listen to the order in which content is announced, asking the same question. If either sequence feels wrong, the fix is usually to change the HTML order to match the visual order, or to remove the reordering and accept the HTML order visually. The reordering properties are powerful, but their power is exactly what makes them dangerous: they let you create a visual page that contradicts its own document, and only testing with the keyboard and a screen reader reveals the contradiction.

<!-- Keep this order meaningful: title, description, then button. -->
<article>
  <h2>Product</h2>
  <p>Description</p>
  <button>Buy</button>
</article>

Check: Why can reordering items visually cause an accessibility problem?

Section 49 — Common Flexbox mistakesThe classics are worth listing together, because recognising them by name makes them faster to fix. First, forgetting `display: flex` on the parent and wondering why the children ignore flex properties — the properties belong to a flex formatting context, and without the container declaration, there is no such context, so the declarations are simply ignored. Second, putting `justify-content` or `align-items` on an item instead of the container — those are container properties, and on an item they do nothing, while the item properties you probably meant are `align-self` and auto margins. Third, expecting `align-content` to do something on a single line, when it only acts on multiple lines. Fourth, fighting the content minimum size instead of setting `min-width: 0` or addressing the content that sets the floor. Fifth, using `flex: 1` when you meant proportional sizing, or `flex: auto` when you meant equal sizing, and being surprised by the result. Each of these has a section in this chapter, and each is fixed by returning to the model: container versus item, main versus cross axis, basis versus final size.

Beyond those five, there are subtler mistakes that appear as layouts grow. One is over-nesting: wrapping every item in extra divs "just in case", until the flex levels multiply and the structure becomes hard to follow. Every wrapper should do a layout job, and a wrapper that does nothing should be removed. Another is setting fixed heights on items that should stretch, which defeats the default alignment and produces cards of uneven height for no reason — if you want equal heights, let stretch do its job. Another is mixing gap with margins on the same items, producing double spacing that is hard to account for, when gap alone would have sufficed. And another is writing media queries that change too much at once — direction, justification, alignment, and sizing all in one breakpoint — when changing one property would have achieved the responsive behaviour, making the breakpoint harder to understand and maintain.

The deepest common mistake is not a property error but a thinking error: treating Flexbox as a set of tricks rather than a model. Developers who learn "this snippet centres things" and "this snippet makes equal columns" without learning the axes, the container-item relationship, and the sizing calculations will be stuck whenever a layout differs from the snippets they know. Developers who learn the model can derive the snippet for any new layout, because the model generates the answers: which axis, which side of the relationship, which starting size, which distribution of spare space. That is why this chapter spent fifty sections on the model rather than presenting a cookbook of snippets. Snippets are useful, but they are outputs of understanding, not substitutes for it, and the time spent on the model pays for itself the first time you face a layout you have never seen before.

/* flex properties for arrangement go on the CONTAINER. */
.container {
  display: flex;
  justify-content: center;
}

Check: If justify-content seems to do nothing, what is the first thing to check?

Section 50 — Flexbox in one paragraphFlexbox turns a parent into a container whose children flow along a main axis, spaced by justify-content, aligned on the cross axis by align-items, wrapped if you ask, and sized by grow, shrink, and basis — with order and align-self for exceptions. That sentence is the whole chapter in miniature, but a summary deserves more than a sentence, because the value of this chapter is not in memorising the sentence but in understanding each of its clauses well enough to use them independently. The container-item relationship is the foundation: the container decides direction, spacing, alignment, and wrapping, while the items decide their own growth, shrinkage, starting size, and individual alignment. Every property in the chapter belongs to one side or the other, and knowing which side is the first step in using it correctly. The axes are the second foundation: main and cross, following the direction rather than the screen, so that justification always means "along the flow" and alignment always means "across the flow", in rows and columns alike.

The cautions of the chapter deserve a place in the summary too, because they are what separate a working layout from a good one. Visual order is not reading order, so reordering tools demand care and testing. Content minimum sizes protect readability, so overflows should be answered by addressing content or layout, not by forcing sizes. Spare space is a precondition for alignment, so properties that seem broken are often simply without room to act. And Flexbox itself has a boundary: one direction at a time, with Grid waiting for the two-dimensional jobs. Keeping those cautions in mind while you build will prevent more problems than any amount of property knowledge alone, because most layout failures are not failures of knowing a property — they are failures of applying the model carefully, or of choosing the wrong model for the job.

The sizing story is the third foundation, and it is the part most worth revisiting. Items start from a basis — their content, a width, or an explicit basis — and then grow into spare space or shrink into shortage according to their grow and shrink values, never below their content floor unless you explicitly permit it. The shorthand flex packages those three decisions, and its four common forms — initial, none, auto, and 1 — cover the sizing needs of nearly every item you will build, from fixed icons to equal columns to proportional navigation links.

As you move to the next chapter, carry one exercise with you: build the practical task below, then rebuild it in Grid, and compare the two versions. Notice where Flexbox felt natural — the header row, the card row's wrapping, the button pushed to the bottom of a card — and where Grid would have felt more natural, such as aligning cards across rows. That comparison is the real lesson of these two chapters together: not that one tool is better, but that each tool has a shape of problem it fits, and a developer who knows both shapes can choose well.

Check: In your own words, what are the three sizing properties of a flex item?

Practical task

Build a small product page header and card row using only Flexbox. The header must have a logo on the left and three navigation links on the right, vertically centred. Below it, build a row of three cards that wrap onto new lines on a narrow screen, all the same height, each with a title, a short paragraph and a button pushed to the bottom of the card. Use gap for spacing, not margins between items. Then resize the browser window and check that the cards wrap, and use Inspect to confirm which element is the flex container at each level.

Chapter questions

  1. Explain the difference between justify-content and align-items, using the main axis and the cross axis.
  2. A row of cards overflows its container on a narrow screen. Name two flex properties you could change to fix it, and explain each.
  3. Write the CSS for a container that centres a single item both horizontally and vertically.
  4. A sidebar must stay 220 pixels wide while the main content fills the rest. Write the flex declarations for both, and explain the numbers.
  5. Why should you be careful about using order to rearrange items on a page?