027: Flexbox I
Learning outcomes
By the end of this lesson, you should be able to identify a flex container and its flex items, name the main and cross axes, choose a row or column direction, distribute and align items, use gap and wrapping, and build a navigation and card row without changing the logical order of the source.
Prerequisites and retrieval
Bring forward the normal-flow model and the reason absolute positioning is a poor fit for page-level rows. Also keep the fluid constraints from 024 in mind. Flexbox changes the arrangement of a container's direct children; a grandchild is not a flex item unless its own parent is made a flex container as well.
Terminology
- Flex container: “This value causes an element to generate a flex container box” (display: flex / inline-flex). — Source: CSS Flexbox 1
- Flex item: Each in-flow child of a flex container becomes a flex item. — Source: CSS Flexbox 1
- Main axis: The primary axis along which flex items are placed, set by flex-direction. — Source: CSS Flexbox 1: Axes
- Cross axis: The axis perpendicular to the main axis. — Source: CSS Flexbox 1: Axes
- Main/cross start and end: Direction-aware edges of the container along each axis. — Source: CSS Flexbox 1: Axes
justify-content: Aligns items along the main axis when there is free space. — Source: CSS Flexbox 1: justify-contentalign-items: Default cross-axis alignment for all items in a line. — Source: CSS Flexbox 1: align-itemsgap: Spacing between adjacent flex/grid items, not around edges. — Source: CSS Box Alignment- Flex line: Items sharing one main-axis run; wrapping creates multiple lines. — Source: CSS Flexbox 1: Flex lines
- Align-content (
align-content): "Aligns flex lines as a group along the cross axis when there is extra space." — Source: MDN: Aligning items — align-content - Align-self (
align-self): "Overrides align-items for a single flex item." — Source: MDN: align-self - Order (
order): "Specifies the order of a flex item, without affecting source/DOM order." — Source: CSS Flexible Box Layout: order
Mental model: distribute items along one primary axis
Flexbox is one-dimensional. It lays items out along one primary axis, either a row or a column, even if wrapping produces several lines. That axis comes from flex-direction, so it is not necessarily horizontal: with row, the main axis is the inline direction; with column, it is the block direction. As a result, justify-content does not universally mean horizontal alignment, and align-items does not universally mean vertical alignment.
A reliable starting point is to identify the direct children and decide which direction should carry the primary relationship. From there, choose direction, wrapping, gap, distribution, and cross-axis alignment. Do not reverse directions or use order merely to obtain a preferred visual arrangement. Doing so can separate visual order from keyboard and reading order.
Beginner example: portfolio navigation
<header class="site-header">
<a class="brand" href="/">Asha Rao</a>
<nav aria-label="Primary">
<ul class="nav-list">
<li><a href="#work">Work</a></li>
<li><a href="#about">About</a></li>
<li><a href="#contact">Contact</a></li>
</ul>
</nav>
</header>
.site-header {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
gap: 1rem 2rem;
padding: 1rem;
}
.nav-list {
display: flex;
flex-wrap: wrap;
gap: 0.5rem 1rem;
margin: 0;
padding: 0;
list-style: none;
}
.nav-list a {
display: inline-block;
padding: 0.5rem;
}
The header's two direct flex items are the brand and the nav. When there is room, space-between puts the available space between them. When there is not, wrapping lets the nav move to another line instead of forcing an overflow. The list is a separate flex container, this time for its li children. None of that changes the semantic navigation and list structure.
Resize the page and increase the text size while testing this example. gap stays between neighboring items; the padding provides edge space and a larger interaction target. If the header wraps sooner than you expected, that is valid content-driven behavior. Lesson 031 can introduce a layout change later if the design genuinely needs one.
Intermediate example: wrapping project cards
<section class="project-list" aria-label="Selected projects">
<article class="project-card">...</article>
<article class="project-card">...</article>
<article class="project-card">...</article>
</section>
.project-list {
display: flex;
flex-wrap: wrap;
align-items: stretch;
gap: 1rem;
}
.project-card {
flex: 1 1 18rem;
max-inline-size: 32rem;
padding: 1rem;
border: 1px solid rgb(203 213 225);
background: white;
}
The declaration flex: 1 1 18rem is shorthand for grow, shrink, and basis. While collecting a line, the wrapping algorithm considers each item's outer hypothetical main size. It keeps adding items until the next item no longer fits, then starts another line. Once the lines exist, Flexbox resolves flexible lengths separately on each line: items can grow into positive free space or shrink to resolve negative free space, within their minimum and maximum sizes. The result responds to available space and content, not to a named device. When the cross size is auto, align-items: stretch is the default and can give cards on one line a shared line height. There is no need to add fixed heights.
Because each wrapped line is resolved independently, columns on separate lines do not have to align. When row-and-column alignment is the central requirement, Grid in 029 is the better tool.
Optional advanced example: auto margin
Within a flex row, an auto margin consumes available space on its axis:
.toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: 0.75rem; }
.toolbar__contact { margin-inline-start: auto; }
This can push one item toward inline-end without using space-between. Test the behavior when the row wraps, though. Keep the technique when the grouping remains obvious; if it does not, separate the groups semantically instead.
Mistakes, debugging, and DevTools
- Applying
display: flexto the items rather than to their parent. - Assuming grandchildren are flex items automatically.
- Losing track of the axes after setting
flex-direction: column. - Treating
justify-content: space-betweenas a replacement for padding that defines a target's edge space. - Omitting
flex-wrap, which can compress or overflow labels. - Using
orderorrow-reverseto repair an HTML sequence. - Adding fixed card heights to align their bottoms instead of letting stretching and content determine the size.
- Selecting Flexbox when strict two-dimensional alignment is the actual requirement.
DevTools can display the flex axes, lines, gaps, and item sizes. Select the flex container, turn on its flex badge, and inspect the direct children. Toggle wrapping and resize the container. Computed styles show the resolved flex basis and size. Intrinsic minimum sizes can still prevent an item from shrinking, which is the next detail to investigate tomorrow.
Accessibility and performance
Keep the DOM order the same as the reading and interaction order. Visual flex reordering does not reliably change screen-reader order or sequential keyboard focus. Retain semantic <nav> and list markup, allow labels to wrap, keep focus indicators visible, and use padding to provide an adequate target size.
Flex layout is efficient for ordinary interfaces. A very large layout that changes continuously can require substantial work, but images, scripts, and visual effects are usually larger contributors. Do not start by optimizing Flexbox; measure first, and avoid animating dimensions across many flex items without evidence that it is necessary.
Deep dive: Flexbox property map
Container properties:
.container {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: flex-start;
align-items: stretch;
align-content: normal;
gap: 1rem;
}
Item properties:
.item {
flex-grow: 0;
flex-shrink: 1;
flex-basis: auto;
align-self: auto;
order: 0;
}
The most common beginner mistake here is putting an item property on the container, or a container property on a child. When a declaration seems to do nothing, first check which element owns the property.
Deep dive: justify-content versus align-content
justify-content distributes free space along the main axis.
align-items aligns items within one flex line along the cross axis.
align-content distributes multiple flex lines along the cross axis. With only one line, it normally has little visible effect.
.gallery {
display: flex;
flex-wrap: wrap;
min-block-size: 30rem;
align-content: space-between;
}
The distinction becomes useful as soon as wrapping creates multiple rows or columns.
Worked example: equal-height action cards without fixed height
<div class="cards">
<article class="card">
<h2>Starter</h2>
<p>Short description.</p>
<a href="/starter">Choose Starter</a>
</article>
<article class="card">
<h2>Pro</h2>
<p>A much longer description that wraps onto several lines.</p>
<a href="/pro">Choose Pro</a>
</article>
</div>
.cards {
display: flex;
flex-wrap: wrap;
align-items: stretch;
gap: 1rem;
}
.card {
flex: 1 1 16rem;
display: flex;
flex-direction: column;
}
.card > a {
margin-block-start: auto;
}
Cards stretch within a flex line. Each card is also a column flex container, so the auto top margin on its action absorbs the remaining space along that vertical main axis and pushes the link to the bottom. This achieves the alignment without imposing a fixed card height.
Worked example: toolbar that wraps instead of squeezing
.toolbar {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 0.5rem;
}
.toolbar__search {
flex: 1 1 16rem;
min-inline-size: 0;
}
.toolbar__actions {
display: flex;
gap: 0.5rem;
}
The search area is allowed to grow, shrink, and move onto a new line. min-inline-size: 0 removes the intrinsic-content minimum that could otherwise prevent it from shrinking enough for the toolbar to wrap cleanly.
Deep dive: source order versus visual order
.feature {
display: flex;
flex-direction: row-reverse;
}
or:
.feature__image {
order: -1;
}
Both approaches can change the visual position without changing the DOM order. That can produce different:
- visual reading order;
- keyboard focus order;
- screen-reader/source order.
Write source order that remains meaningful without CSS. Prefer Grid placement or an alternate markup pattern only when the resulting experience is still coherent and accessible. order should not be a general-purpose responsive content-reordering tool.
Flex debugging checklist
Before changing declarations, describe the layout in this order:
- Which element is the flex container?
- Which direct children are its flex items?
- What is the main axis?
- Is wrapping enabled?
- Is there positive free space, or negative free space to resolve?
- Is the problem main-axis distribution (
justify-content) or cross-axis alignment (align-items)? - Is an intrinsic minimum preventing an item from shrinking?
- Would one item need
margin-inline-start: autoinstead of a more complicated distribution rule?
If those answers are not clear, adding more flex properties will usually obscure the layout rather than fix it.
Tiered exercises
Checkpoint: narrate the axes
For every Flexbox exercise, point to the container and say: “The direct children are ..., the main axis is ..., the cross axis is ...”. Translate each declaration into that model. In a row, justify-content: space-between distributes main-axis free space between items, while align-items: center aligns item margin boxes on the cross axis. Change the direction to column: the property names remain the same, but their physical directions change.
Test the navigation in five states: three short links, six links, one translated long label, 200% text, and a container only 18rem wide. The goal is not to preserve one row. It is readable wrapping, adequate targets, and logical focus order. gap keeps spacing consistent between wrapped items, but it does not provide edge padding; the container still owns its inset.
Now compare three card declarations. With flex: 0 1 18rem, cards prefer 18rem and do not consume positive free space. With flex: 1 1 18rem, they can grow and shrink. With flex: 1 1 0, equal growth starts from a zero basis, although intrinsic minimums can still influence the result. Inspect the actual used sizes rather than calling any of these fixed widths.
Flex wrapping is not a true table. Each line calculates free space on its own, so a final line containing one card can become wider than the cards above it. Constrain the maximum card size if that harms readability, or use Grid when shared columns and consistent track alignment are requirements.
Do not add order to put a featured card first unless it is also first in meaningful source order. Keyboard focus follows the DOM order, and a screen reader generally announces that same order. If the content priority has changed, change the HTML.
Alignment needs available space. justify-content cannot visibly distribute positive free space when the items already consume the full main size. Cross-axis alignment can likewise appear to do nothing when the container has no extra cross size. Inspect the container and item dimensions before changing properties. Do not add arbitrary height just to make an alignment demonstration work in production.
Baseline alignment is useful for rows containing text at different sizes:
.metadata-row {
display: flex;
flex-wrap: wrap;
align-items: baseline;
gap: 0.5rem 1rem;
}
This aligns text baselines rather than box centers. Test replaced elements and multiline labels too, because their baseline behavior can differ from a simple one-line example.
With flex-direction: column, percentages and auto margins may behave differently from familiar row recipes. Name the main axis first, then determine whether free space exists. In a column card, margin-block-start: auto on the action pushes along the vertical main axis only when the card or content area has extra block space.
End by disabling display: flex. The DOM should fall back to a readable normal-flow stack. This check verifies that Flexbox is providing presentation, not masking broken semantics.
Foundation: Turn a semantic navigation list into a wrapping flex row with gap and padded links.
Core: Create a wrapping project-card container. Cards should prefer 18rem, grow, shrink, and never use fixed height.
Stretch: Add a toolbar with one contact action pushed to inline-end by auto margin. Test its behavior when it wraps and document whether the result remains understandable.
.site-header { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 1rem 2rem; }
.nav-list { display: flex; flex-wrap: wrap; gap: .5rem 1rem; margin: 0; padding: 0; list-style: none; }
.nav-list a { display: inline-block; padding: .5rem; }
.project-list { display: flex; flex-wrap: wrap; align-items: stretch; gap: 1rem; }
.project-card { flex: 1 1 18rem; max-inline-size: 32rem; padding: 1rem; border: 1px solid rgb(203 213 225); }
.toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: .75rem; }
.toolbar__contact { margin-inline-start: auto; }
a:focus-visible { outline: 3px solid rgb(234 88 12); outline-offset: 3px; }
Recap and exit questions
Flexbox arranges direct children along one primary axis. When axis-aware distribution is combined with wrapping, gap, and intrinsic content, it can produce resilient rows and columns without offsets.
- Which elements are the flex items in the navigation example?
- Along which axis does
justify-contentoperate? - Why does navigation need
flex-wrap? - What behavior does
flex: 1 1 18remdescribe? - When is Grid a better choice than wrapping Flexbox?
