028: Flexbox II
Learning outcomes
By the end of this lesson, you should be able to reason about flex basis, growth, shrinkage, and intrinsic minimums; size individual items deliberately; align an exceptional item; build a card whose action stays after flexible content; and diagnose common overflow without reaching for arbitrary widths.
Prerequisites and retrieval
Before continuing, retrieve the roles of the flex container and flex items, the main and cross axes, wrapping, and gap. Also be able to read flex: 1 1 18rem as three separate values. The aim here is to inspect the sizing negotiation, not to collect another set of alignment recipes.
Terminology
- Flex base size: The starting main size derived from
flex-basisbefore the browser distributes growth or shrinkage. — Source: CSS Flexbox 1: Flex base size - Free space: The part of the container's main size left after item bases, margins, and gaps are accounted for. — Source: CSS Flexbox 1: Resolving flexible lengths
- Grow factor: The proportional share of positive free space assigned to an item through
flex-grow. — Source: CSS Flexbox 1: flex-grow - Shrink factor: An item's weighted participation when the available free space is negative, controlled by
flex-shrink. — Source: CSS Flexbox 1: flex-shrink - Hypothetical size: The flex base size after it has been clamped by the applicable minimum and maximum sizes, before negotiation. — Source: CSS Flexbox 1: Hypothetical main size
- Intrinsic minimum: The automatic min-content floor that prevents an item from collapsing unless
min-inline-sizeis overridden. — Source: CSS Flexbox 1: Automatic minimum size align-self: A per-item override of the flex container'salign-itemsvalue on the cross axis. — Source: CSS Flexbox 1: align-self- Auto margin: A margin that absorbs available space along its axis before alignment distributes any space that remains. — Source: CSS Flexbox 1: Auto margins
- Intrinsic sizing (
min-content/max-content): "min-contentis the narrowest size without overflow;max-contentis the size needed to fit content without line breaks." — Source: CSS Sizing Level 3: Intrinsic sizes
Mental model: basis first, then negotiate free space
Flex sizing starts with a basis for each item. Once those starting sizes are known, the browser distributes positive free space according to grow factors. If the items do not fit, shrink factors and the base sizes influence how much each item gives up, subject to minimum and maximum constraints. flex-grow: 1 does not mean “make this item 100% wide,” and equal growth factors do not promise equal final widths when bases or content differ.
These common declarations communicate different intentions:
flex: 0 1 auto; /* default: do not grow, may shrink, content/width basis */
flex: 1 1 0; /* share from zero basis; useful for equal shares */
flex: 1 1 18rem;/* prefer 18rem, then negotiate */
flex: none; /* 0 0 auto: do not grow or shrink */
Use the shorthand while you are reasoning about a layout so all three values stay visible. Content remains part of the calculation: flex items default to min-width: auto, which often preserves their min-content width.
Beginner example: media object
<article class="profile">
<img class="profile__photo" src="asha.jpg" width="160" height="160" alt="Asha Rao">
<div class="profile__body">
<h2>Asha Rao</h2>
<p>Documenting accessible interface decisions and handling averyverylongunbrokenidentifier.</p>
</div>
</article>
.profile {
display: flex;
align-items: flex-start;
gap: 1rem;
max-inline-size: 42rem;
}
.profile__photo {
flex: 0 0 5rem;
inline-size: 5rem;
block-size: 5rem;
border-radius: 50%;
object-fit: cover;
}
.profile__body {
flex: 1 1 auto;
min-inline-size: 0;
}
.profile__body p { overflow-wrap: anywhere; }
The image has an intentional avatar size and is not allowed to shrink. The body receives the remaining space. min-inline-size: 0 lets that flex item shrink below its automatic min-content minimum, after which the long token can wrap. This is not a declaration to paste into every flex layout. Add it when inspection shows that a flex child's intrinsic minimum is the thing causing overflow.
Remove min-inline-size: 0 and test at a narrow width. Does the body force the container wider? Restore it, then remove overflow-wrap and test again. Those experiments separate two problems: permission for the item to shrink, and an opportunity for the text itself to break.
Intermediate example: cards with actions after flexible content
<article class="project-card">
<img src="library.jpg" width="960" height="540" alt="Library finder results page">
<div class="project-card__content">
<p class="project-card__meta">Accessibility · HTML</p>
<h2>Library finder</h2>
<p>Search, opening hours, directions, and route accessibility.</p>
<a class="button-link" href="#">Read case study</a>
</div>
</article>
.project-list {
display: flex;
flex-wrap: wrap;
align-items: stretch;
gap: 1rem;
}
.project-card {
display: flex;
flex: 1 1 18rem;
flex-direction: column;
max-inline-size: 32rem;
border: 1px solid rgb(203 213 225);
background: white;
}
.project-card__content {
display: flex;
flex: 1;
flex-direction: column;
align-items: flex-start;
padding: 1rem;
}
.project-card__content .button-link {
margin-block-start: auto;
}
This example contains nested one-dimensional layouts. The list lays out the cards. Each card puts its image above its content, and the content then stacks its text and action. Because the content flexes to fill the stretched card height, the action's auto block-start margin consumes the remaining main-axis space. The action still follows the description in DOM order, so there is no need for absolute positioning.
When matching button positions across cards is not a requirement, leave out the nested flex behavior and keep normal flow. Simpler layout is preferable when it already expresses the interface.
Optional advanced example: unequal sidebar relationship
.split {
display: flex;
flex-wrap: wrap;
gap: 2rem;
}
.split__main { flex: 3 1 32rem; min-inline-size: 0; }
.split__aside { flex: 1 1 16rem; }
The factors are proportional only while the browser is distributing the relevant free space. The bases and constraints still shape the result. Wrapping allows the aside to move below the main content when both preferred bases no longer fit. Grid may communicate strict tracks more clearly in 029.
Mistakes, debugging, and DevTools
- Treating grow factors as percentages.
- Setting only
flex-growand forgetting that the basis remainsauto. - Applying
min-inline-size: 0everywhere instead of locating the item that overflows. - Using
flex: noneon long navigation labels, preventing them from shrinking or wrapping. - Setting equal heights explicitly instead of using stretch and natural growth.
- Applying
align-selfwhen the desired movement is on the main axis;align-selfcontrols the cross axis. - Nesting flex containers where normal flow would be sufficient.
- Using
orderfor responsive rearrangement and thereby breaking the focus sequence.
Turn on the Flexbox overlay and inspect the item base sizes, final sizes, and constraints. Change flex-basis between 0, auto, and 18rem and watch how the available space is allocated. DevTools may expose a Flexbox editor and growth/shrink calculations. Test with a long word, a larger image, a missing image, and 200% zoom; each case can reveal a different sizing assumption.
Accessibility and performance
The DOM remains the authoritative reading and focus order. Keep actions after their descriptions in the markup. An informative cropped image still needs meaningful alt text; a decorative image should have empty alt text. Avoid fixed card heights so enlarged text can remain visible, and make sure a pushed action never overlaps its content.
Nested flex layout is perfectly reasonable when each container has a clear job, but do not add wrappers without a purpose. Avoid continuously animating flex-basis, width, or height across many items because those properties trigger layout work. Prefer restrained transforms for optional motion, and honor reduced-motion preferences whenever animation is present.
Deep dive: what flex shorthand really means
The shorthand is common:
.item {
flex: 1;
}
But it expands to a grow/shrink/basis combination. It does not magically mean “make equal columns.” To reason about the behavior, write the values explicitly:
.item {
flex: 1 1 0;
}
versus:
.item {
flex: 1 1 auto;
}
The key difference is the basis used before free space is shared.
flex-basis: 0begins negotiation from zero-sized bases, so equal grow factors tend to produce equal shares.flex-basis: autouses the item's main-size or intrinsic basis when available, allowing content to influence the starting size.
Deep dive: flex sizing algorithm as a practical story
You do not need to recite the specification algorithm from memory. You do need a reliable sequence for interpreting what DevTools shows:
- Determine each item's flex base size.
- Add the gaps and bases to determine whether the container has positive or negative free space.
- If free space is positive,
flex-growparticipates. - If free space is negative,
flex-shrinkparticipates relative to the base sizes. - Minimum and maximum constraints can freeze items and force the remaining space to be redistributed.
- Alignment takes place after the sizes are resolved.
That sequence explains why “all children have flex: 1” can still produce a surprise when one child has a large minimum width or unbreakable content.
Worked example: fixed-ish sidebar with flexible content
<div class="dashboard">
<aside class="dashboard__sidebar">...</aside>
<main class="dashboard__main">...</main>
</div>
.dashboard {
display: flex;
flex-wrap: wrap;
gap: 2rem;
}
.dashboard__sidebar {
flex: 0 1 18rem;
}
.dashboard__main {
flex: 1 1 32rem;
min-inline-size: 0;
}
Read the declarations as a relationship rather than as fixed widths:
- The sidebar does not grow, may shrink, and prefers 18rem.
- The main area grows, may shrink, and prefers 32rem.
- Wrapping lets the main area move below when those preferred sizes no longer fit together.
This produces a responsive relationship without requiring a breakpoint.
Worked example: equal buttons versus content-sized buttons
Equal distribution:
.button-row {
display: flex;
gap: 0.75rem;
}
.button-row > * {
flex: 1 1 0;
}
Content-sized:
.button-row {
display: flex;
gap: 0.75rem;
}
.button-row > * {
flex: 0 0 auto;
}
Neither choice is universally correct. Equal widths can make a compact dialog feel balanced, while content-sized controls are often easier to use in a toolbar.
Deep dive: shrinking is constrained by content
This pattern looks reasonable and still fails surprisingly often:
.row {
display: flex;
}
.row__content {
flex: 1;
}
If .row__content contains a long URL or wide preformatted content, its automatic minimum size may prevent it from shrinking.
Add:
.row__content {
flex: 1 1 auto;
min-inline-size: 0;
}
.row__content p {
overflow-wrap: anywhere;
}
min-inline-size: 0 is not a ritual incantation. It tells the flex item that shrinking below its content-based automatic minimum is allowed.
Deep dive: align-self and exceptional items
.row {
display: flex;
align-items: center;
}
.row__badge {
align-self: flex-start;
}
Use align-self when one item genuinely needs a different cross-axis position from the container default. If every child needs its own align-self value, the container's alignment model may be expressing the wrong design.
Flexbox versus Grid decision examples
Choose Flexbox when:
- navigation items need one-axis distribution;
- buttons need wrapping;
- an avatar and text need a media-object relationship;
- a card needs a column with its action pushed to the bottom.
Choose Grid when:
- card columns must line up across rows;
- page regions need two-dimensional placement;
- repeated items need explicit rows and columns;
- overlapping named areas are clearer than independent flex lines.
A component can use Grid outside and Flexbox inside. The two layout systems compose; choosing one does not rule out the other at a different level.
Tiered exercises
Checkpoint: trace the sizing negotiation
Imagine a 60rem container with a 2rem total gap and two items whose bases are 20rem and 10rem. Both have a grow factor of 1. Before constraints, 28rem remains. Equal grow factors give each item 14rem, producing final sizes of 34rem and 24rem, not equal widths. Equal factors share free space; they do not erase different bases. Starting from a zero basis changes that relationship.
Shrinkage is not simply an equal subtraction either. The algorithm uses scaled shrink factors based on the base sizes, then freezes items at their minimum or maximum constraints when necessary. You will rarely calculate every iteration by hand, but this model explains why a large item often gives up more space and why a content minimum can halt the negotiation.
Build a reproducible overflow test: put a long, unbroken identifier inside the flexible body while a fixed avatar takes up space. Inspect the body's computed minimum and used width. Set min-inline-size: 0; if the item now shrinks but the text still paints outside, add an appropriate wrapping rule to the text. These declarations address different layers of the problem.
For card actions, compare an auto margin with absolute positioning. The auto margin participates in flex layout, follows descriptions as they grow, and preserves the card's flow. Absolute positioning removes the action from flow, which leads to guessed bottom padding and possible overlap. The Flexbox version is shorter and responds to content.
Constraints can freeze an item during negotiation. Add max-inline-size to one growing item and watch it stop accepting space while its siblings continue. Add a realistic min-inline-size to a sidebar and observe wrapping happen sooner. Minimums and maximums should describe content requirements, not an attempt to reproduce one screenshot exactly.
align-self overrides the container's align-items for one item. Use it when one action or image has a real cross-axis alignment exception, not to compensate for inconsistent markup. It cannot distribute main-axis free space; use an auto margin or container justification for that axis.
Nested flex layouts can make it unclear which container controls a property. In DevTools, move up one level at a time and label each set of direct children. A card can be a flex item in the gallery and a flex container for its own content at the same time. Its flex shorthand belongs to the outer relationship; its flex-direction controls the inner one. Separating those roles makes the debugging path much faster.
Foundation: Build the profile media object with a nonshrinking 5rem image and a flexible body. Add a long token, then repair the overflow intentionally.
Core: Make project cards column flex containers. Push each action after the flexible content without positioning or fixed heights.
Stretch: Build a wrapping main/aside split with different basis and grow values. Explain what happens at wide, medium, and narrow available widths.
.profile { display: flex; align-items: flex-start; gap: 1rem; }
.profile__photo { flex: 0 0 5rem; inline-size: 5rem; block-size: 5rem; object-fit: cover; }
.profile__body { flex: 1 1 auto; min-inline-size: 0; }
.profile__body p { overflow-wrap: anywhere; }
.project-card { display: flex; flex: 1 1 18rem; flex-direction: column; }
.project-card__content { display: flex; flex: 1; flex-direction: column; align-items: flex-start; padding: 1rem; }
.project-card__content .button-link { margin-block-start: auto; }
.split { display: flex; flex-wrap: wrap; gap: 2rem; }
.split__main { flex: 3 1 32rem; min-inline-size: 0; }
.split__aside { flex: 1 1 16rem; }
Recap and exit questions
Flex items begin with a basis, then negotiate positive or negative free space under constraints. Intrinsic minimums account for many “Flexbox overflow” bugs. Nested flex is useful when each level has a clear one-axis responsibility.
- Why do equal grow factors not always produce equal final widths?
- What does
min-inline-size: 0allow? - How does an auto margin on the main axis behave?
- Why is the card action not absolutely positioned?
- When is
flex: noneappropriate?
