027: Flexbox I
Learning outcomes
By the end, you can identify flex container, items, main axis, and cross axis; choose row or column direction; distribute and align items; use gap and wrapping; and build a navigation and card row without changing logical source order.
Prerequisites and retrieval
Retrieve normal flow and why absolute positioning is unsuitable for page rows. Keep 024's fluid constraints. Flexbox changes how a container's direct children are arranged; grandchildren are not flex items unless their parent also becomes a flex container.
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 controls a row or column, even when wrapping creates multiple lines. The main axis follows flex-direction, not always horizontal. With row, main is the inline direction; with column, main is the block direction. Therefore justify-content does not mean horizontal and align-items does not mean vertical.
Start by identifying the direct children and desired primary axis. Then choose direction, wrapping, gap, distribution, and cross alignment. Avoid changing source order with order or reverse directions just for appearance; visual and keyboard/reading orders can diverge.
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 has two direct items: brand and nav. Space is distributed between them when available; wrapping permits the nav to move to another flex line instead of overflowing. The list separately becomes a flex container for its li children. Semantic list/navigation structure remains.
Resize and increase text size. gap remains only between items. Padding supplies edge and target space. If the header wraps earlier than visually expected, that is acceptable content-driven behavior; 031 can add a layout change only if needed.
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;
}
flex: 1 1 18rem is shorthand for grow, shrink, and basis. For line collection, the wrapping algorithm considers each item's outer hypothetical main size; it keeps adding items until the next one would not fit, then starts another line. After lines are collected, Flexbox resolves flexible lengths independently on each line: items may grow into positive free space or shrink to resolve negative free space, subject to their minimum and maximum sizes. Wrapping therefore responds to available space and content, not a named device. align-items: stretch is the default when cross size is auto and can make cards on one line share line height; do not add fixed heights.
Because wrapping Flexbox treats each line independently, columns on different lines may not align. Grid in 029 is better when row-and-column alignment is central.
Optional advanced example: auto margin
Inside a flex row, an auto margin absorbs available main-axis space:
.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 space-between, but wrapping must be tested. Keep it when the relationship is clear; otherwise separate groups semantically.
Mistakes, debugging, and DevTools
- Applying flex to the items instead of their parent.
- Expecting grandchildren to become flex items.
- Confusing axes after
flex-direction: column. - Using
justify-content: space-betweenas a substitute for target padding. - Forgetting
flex-wrap, causing compressed or overflowing labels. - Using
orderorrow-reverseto fix HTML sequence. - Using fixed card heights to align bottoms; let flex stretching and content work.
- Choosing Flexbox for strict two-dimensional alignment.
Browser DevTools can overlay axes, lines, gaps, and item sizes. Select the flex container, enable its badge, and inspect direct children. Toggle wrapping and resize. Computed styles expose the resolved flex basis and size; remember that intrinsic minimum sizes may prevent shrinking, a topic continued tomorrow.
Accessibility and performance
Keep DOM order equal to reading and interaction order. Flex visual reordering does not reliably reorder screen-reader or sequential focus navigation. Preserve semantic <nav> and list markup. Allow labels to wrap, maintain visible focus, and use padding for target size.
Flex layout is efficient for ordinary interfaces. Very large changing layouts can incur work, but images, scripts, and effects usually dominate. Avoid animating dimensions of many flex items; measure before optimizing.
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 error is applying an item property to the container or a container property to a child.
Deep dive: justify-content versus align-content
justify-content distributes free space along the main axis.
align-items aligns items within a flex line along the cross axis.
align-content distributes multiple flex lines along the cross axis. It does little when there is only one line.
.gallery {
display: flex;
flex-wrap: wrap;
min-block-size: 30rem;
align-content: space-between;
}
That distinction matters whenever wrapping creates multiple rows/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;
}
The cards stretch within a flex line. Inside each card, the auto top margin consumes remaining main-axis space, pushing the action to the bottom without 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 can grow, shrink, and wrap. min-inline-size: 0 prevents intrinsic content from blocking shrinkage.
Deep dive: source order versus visual order
.feature {
display: flex;
flex-direction: row-reverse;
}
or:
.feature__image {
order: -1;
}
can visually reorder content without changing DOM order. That can create confusing differences between:
- visual reading order;
- keyboard focus order;
- screen-reader/source order.
Use source order that is meaningful without CSS. Prefer Grid placement or alternate markup patterns only when accessibility remains coherent. Do not use order as a general responsive content-reordering tool.
Flex debugging checklist
Before changing properties, narrate:
- Who is the flex container?
- Which direct children are flex items?
- What is the main axis?
- Is wrapping enabled?
- Is there free space or negative free space?
- Is the problem distribution (
justify-content) or cross alignment (align-items)? - Is an item's intrinsic minimum preventing shrinkage?
- Does one item need
margin-inline-start: autorather than a complex distribution rule?
If you cannot answer those questions, adding more flex properties will usually make the layout harder to understand.
Tiered exercises
Checkpoint: narrate the axes
For every Flexbox exercise, point at the container and say: “The direct children are ..., the main axis is ..., the cross axis is ...”. Then translate declarations into those terms. In a row, justify-content: space-between distributes main-axis free space between items and align-items: center aligns item margin boxes on the cross axis. Change to column; the property names do not change, but their physical directions do.
Test five states of the navigation: three short links, six links, one translated long label, 200% text, and a container only 18rem wide. The goal is not to keep one row. The goal is readable wrapping, adequate targets, and logical focus order. gap produces consistent spacing between wrapped items; it does not create edge padding, so the container still owns its inset.
Then 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 grow and shrink. With flex: 1 1 0, equal growth begins from a zero basis, though intrinsic minimums can still affect the result. Inspect actual used sizes rather than describing these as fixed widths.
Flex wrapping does not create a true table. Each line calculates free space independently, so a final line with one card can grow wider than cards above. Constrain card maximum size if that outcome harms readability, or use Grid when shared columns and consistent track alignment are requirements.
Do not add order to make a featured card appear first unless it is also first in meaningful source order. A keyboard user's focus follows DOM order, and a screen reader generally announces DOM order. If content priority changed, update the HTML.
Alignment requires available space. justify-content cannot visibly distribute positive free space when items already consume the full main size. Likewise, cross-axis alignment may seem absent when the container has no extra cross size. Before changing properties, inspect container dimensions and item sizes. Do not add arbitrary height merely to make an alignment demo work in production.
Baseline alignment can improve rows containing different text sizes:
.metadata-row {
display: flex;
flex-wrap: wrap;
align-items: baseline;
gap: 0.5rem 1rem;
}
Text baselines align rather than box centers. Test replaced elements and multiline labels because baseline behavior may differ from a simple single-line example.
When flex-direction: column, percentages and auto margins can behave differently from memorized row recipes. State the main axis first, then ask whether free space exists. A column card with margin-block-start: auto on its action pushes along the vertical main axis only when the card/content area has extra block space.
Finish by disabling display: flex. The DOM should return to a readable normal-flow stack. This fallback check confirms that Flexbox expresses presentation rather than compensating for 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 what happens when it wraps and document whether the behavior 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. Axis-aware distribution, wrapping, gap, and intrinsic content produce resilient rows and columns without offsets.
- Who are the flex items in the navigation example?
- What axis does
justify-contentuse? - Why is
flex-wrapimportant for navigation? - What does
flex: 1 1 18remexpress? - When should Grid replace wrapping Flexbox?
