Flexbox and Grid are often taught as collections of declarations: center with these three properties, make cards responsive with this template, add 1fr when columns should share space. Those recipes work until content is longer, a container becomes narrower, or one child has an intrinsic minimum that the recipe did not account for.

A more durable model is that CSS layout solves constraints. The container offers available space. Each item contributes minimum, preferred, and maximum sizes derived from its content and properties. Flexbox distributes space along one primary axis, repeatedly freezing items that reach constraints. Grid sizes shared tracks in two dimensions, combining intrinsic contributions with fixed, flexible, and content-based limits.

The thesis is practical: when a layout surprises you, inspect which size is definite, which contribution is intrinsic, and which minimum prevented distribution. That usually explains the result more precisely than adding another width, overflow, or media query.

Begin with Available Space and Intrinsic Contributions

Before Flexbox or Grid distributes anything, ordinary sizing rules establish inputs. A definite size can be resolved without measuring dependent content: width: 800px is definite, while width: 50% is definite only when the containing block’s relevant size is itself definite. Indefinite percentages can behave like auto during parts of intrinsic sizing, which is one reason a component can differ between a fixed panel and a content-sized one.

Content contributes several useful sizes. The min-content size is roughly the smallest width the content can occupy without avoidable soft wrapping; an unbreakable token can make it large. The max-content size is the width the content would prefer without wrapping at soft opportunities. These are contributions, not always the final used width. Padding, border, box-sizing, aspect ratios, replaced elements, and explicit minimum or maximum properties modify the constraints around them.

Consider a row containing a label and a generated identifier:

html
<div class="record">
  <span class="record__label">Build artifact</span>
  <code class="record__id">sha256:f47ac10b58cc4372a5670e02b2c3d479</code>
</div>

The identifier has few natural break opportunities. Its min-content contribution may therefore be wider than a narrow panel. Neither Flexbox nor Grid can invent a legal line break. The design must choose among allowing breaks, clipping, scrolling, or preserving the token and letting the outer layout grow.

Two defaults cause many surprises. A flex item generally has an automatic main-axis minimum based on its content instead of zero. A grid track written as 1fr has an automatic minimum as well; conceptually it behaves more like minmax(auto, 1fr) than minmax(0, 1fr). These safeguards avoid accidental content overlap, but they can stop a supposedly flexible region from shrinking.

Writing down the constraint chain is an effective first diagnostic:

  1. What establishes the container’s available inline and block size?
  2. Which child has the largest min-content contribution?
  3. Are percentages resolving against definite sizes?
  4. Which min-*, max-*, aspect-ratio, padding, or border rule clamps the result?
  5. Is overflow intended at the item, track, or outer-container boundary?

Follow Flexbox from Base Sizes to Frozen Items

Flexbox lays out each line independently along its main axis. It first determines a flex base size for every item. A non-auto flex-basis usually supplies that input. With flex-basis: auto, the algorithm consults the main-size property and then content sizing as needed. This distinction is why flex: 1 1 0 and flex: 1 1 auto do not mean the same thing: zero basis asks distribution to start equally, while auto basis preserves differing content or declared sizes before free space is shared.

The algorithm also computes each item’s hypothetical main size by clamping its base through applicable minimum and maximum constraints. It sums the outer hypothetical sizes to decide whether the line should grow or shrink. Gaps, margins, padding, and borders consume space too; gap is not an invisible overlay.

When there is positive free space, unfrozen items receive shares according to flex-grow. A grow factor of 2 receives twice the share of a sibling with factor 1; it does not make the final item twice as wide, because their starting bases may differ. When space is negative, shrinkage uses a scaled shrink factor based on flex-shrink multiplied by the flex base size. Larger bases therefore surrender more absolute space when shrink factors are equal.

Distribution is iterative because constraints can interrupt proportional sharing. An item that reaches min-width or max-width is clamped and frozen. The remaining free space is recalculated and redistributed among items that can still change. A useful simplified growth share, before clamping, is

item growth=free space×item growunfrozen grow.item\ growth = free\ space \times \frac{item\ grow}{\sum unfrozen\ grow}.

The shrink calculation follows the same shape but uses scaled shrink factors. Rounding happens when continuous CSS pixel values map to device pixels, so adjacent items may differ by a pixel without the ratio being wrong.

After main sizes resolve, justify-content distributes leftover space around the items. It does not size them. Cross-axis alignment then uses properties such as align-items, align-self, and align-content; the last applies to multiple flex lines, not to items within one line. Baselines, auto margins, wrapping, and writing modes can all change what visual “start” means.

Work a Flex Distribution by Hand

Suppose a content-box row is 760 pixels wide with two 20-pixel gaps and three items:

Item flex-basis flex-grow flex-shrink Constraint
Navigation 180 px 1 1 min-width: 160px
Editor 300 px 2 1 min-width: 240px
Inspector 180 px 1 1 min-width: 140px

The gaps consume 40 pixels, leaving 720 for items. Their bases total 660, so the line has 60 pixels of positive free space. Grow factors total four. Navigation gets 15, Editor gets 30, and Inspector gets 15. The resulting main sizes are 195, 330, and 195 pixels. None reaches a maximum, so no second distribution round is needed.

At a container width of 600 pixels, gaps leave 560 and the line has 100 pixels of negative free space. With equal shrink factors, the scaled factors are 180, 300, and 180. The first provisional shrink amounts are approximately 27.3, 45.5, and 27.3 pixels. Navigation would become about 152.7 pixels, below its 160-pixel minimum, so it clamps at 160 and freezes. The algorithm redistributes the unresolved deficit across Editor and Inspector. The precise specification has additional bookkeeping, but the important behavior is iterative: one minimum changes what every remaining item receives.

This CSS encodes the intended constraints:

css
.workspace {
  display: flex;
  gap: 20px;
}

.workspace__nav {
  flex: 1 1 180px;
  min-width: 160px;
}

.workspace__editor {
  flex: 2 1 300px;
  min-width: 240px;
}

.workspace__inspector {
  flex: 1 1 180px;
  min-width: 140px;
}

If the editor contains a long preformatted line, its default automatic minimum may exceed 240 pixels and force the whole row wider. Setting min-width: 0 on the editor item permits it to shrink below the content-derived minimum; then the editor must own an explicit overflow policy such as overflow: auto. The declaration is not a magic Flexbox reset. It states that the item’s minimum may be zero and that content overflow will be handled elsewhere.

Wrapping changes the problem. With flex-wrap: wrap, items are collected into lines based on their outer hypothetical sizes, then free space is resolved separately within each line. Grow ratios do not align columns across different lines. If shared columns are the invariant, Grid is usually the appropriate algorithm.

Understand Grid as a Shared Track-Sizing Problem

Grid begins with explicit rows and columns, then creates implicit tracks when placement requires them. Items can be positioned explicitly, by named areas or lines, or by auto-placement. Placement and sizing influence each other, but it helps to reason in that order: determine which tracks an item spans, then ask how its intrinsic contribution constrains those tracks.

A track sizing function has a minimum and a maximum. 200px fixes both. minmax(12rem, 1fr) gives a fixed minimum and flexible maximum. auto is content-sensitive. min-content and max-content request the corresponding intrinsic contribution. fit-content(30rem) behaves content-sensitively up to a cap. Flexible fr tracks divide leftover space only after non-flexible sizes, gaps, and relevant intrinsic minimums have been accounted for.

The full track-sizing algorithm handles spanning items, intrinsic minimums, growth limits, flexible expansion, and row dependencies. A useful reasoning sequence is:

  1. Initialize fixed track limits and unresolved intrinsic limits.
  2. Use non-spanning items to establish content-based minimums and growth limits.
  3. Distribute additional requirements from spanning items across the tracks they cross.
  4. Resolve flexible tracks from the remaining space when that axis is definite.
  5. Stretch eligible auto tracks if alignment leaves extra space.

An item spanning several columns does not belong to one column’s intrinsic size. Its contribution is distributed across the spanned tracks according to which tracks are able to grow. This can make an apparently unrelated column wider. Development tools that display track sizes and grid line numbers are far more informative than mentally assigning the item to its first cell.

repeat(auto-fit, minmax(16rem, 1fr)) and auto-fill both create as many potential columns as fit. auto-fit collapses empty repeated tracks so occupied tracks can expand; auto-fill retains the empty track slots. Neither can fit a 16-rem minimum into a container narrower than 16 rem without overflow. Use minmax(min(16rem, 100%), 1fr) when the component must also fit a narrower containing block.

Work a Grid Track Example from Constraints

Consider a 900-pixel content-box dashboard with two 24-pixel gaps and this template:

css
.dashboard {
  display: grid;
  grid-template-columns: 180px minmax(240px, 2fr) minmax(160px, 1fr);
  gap: 24px;
}

Gaps consume 48 pixels and the fixed first track consumes 180, leaving 672 for the two flexible tracks. Their minimums require 400, so 272 pixels remain flexible. With 2fr and 1fr, the second track receives roughly two thirds of that remainder and the third receives one third. Their final sizes are approximately 421.3 and 250.7 pixels. Together with the first track and gaps, they fill 900 pixels.

Now place an unbreakable 520-pixel token in the minmax(240px, 2fr) track. Because that track has a fixed 240-pixel minimum rather than an automatic content minimum, the token does not force the track to 520; it overflows its item unless wrapping or scrolling is allowed. If the track were simply 2fr, its automatic minimum could honor the item’s min-content contribution and leave too little room for the inspector. The choice between 1fr and minmax(0, 1fr) is therefore a choice about minimum constraints, not just syntax.

A common application shell makes that intent explicit:

css
.shell {
  display: grid;
  grid-template-columns: max-content minmax(0, 1fr);
  min-height: 100dvh;
}

.shell__main {
  min-width: 0;
}

.shell__code {
  overflow: auto;
  white-space: pre;
}

The navigation column is content-sized. The main column may contract below its content’s min-content width, and the code region takes responsibility for horizontal scrolling. Without that explicit ownership, overflow can propagate to the page and produce a viewport-wide horizontal scrollbar.

Rows add another constraint: percentages and fractional tracks need a meaningful available block size. grid-template-rows: 1fr auto does not create a remaining-height layout if the grid container’s height is indefinite. In that case, the flexible row may behave according to its content. Establish the intended block-size chain with min-height, a definite parent size, or a different layout invariant rather than adding larger fractions.

Diagnose Overflow, Minimums, and Alignment Failures

Overflow is evidence that constraints cannot all be satisfied, not necessarily a browser defect. Find the first box wider or taller than its containing block and inspect its computed minimum, used track size, white-space policy, and intrinsic descendants. Temporarily outlining every element can locate the boundary, but computed layout panels explain why it happened.

Frequent failure modes have distinct remedies:

Symptom Likely constraint Intentional remedy
Flex child refuses to shrink Automatic main-axis minimum min-width: 0 plus local overflow/wrapping policy
1fr column expands for a token Automatic track minimum minmax(0, 1fr) plus content policy
Percentage height appears ineffective Indefinite containing block height Establish a definite size or use intrinsic layout
Wrapped flex rows do not align Lines size independently Use Grid when shared tracks are required
Grid item appears in an extra row Auto-placement created an implicit track Inspect placement and set grid-auto-* deliberately
Ellipsis does not appear No constrained overflow box Set a shrinkable width, no wrapping, hidden overflow, and text overflow

Avoid globally applying min-width: 0 or overflow: hidden. The first can permit controls or prose to become unusably narrow; the second can clip focus rings, menus, and content without explaining the underlying constraint. Apply them at the component boundary that owns the fallback behavior.

Alignment can also masquerade as sizing. place-items: center centers items within their grid areas; it does not center the grid tracks within the container. place-content distributes the track collection when the grid is smaller than its container. In Flexbox, an auto margin absorbs available main-axis space before justify-content has anything to distribute. Inspect used sizes and free space before changing alignment properties.

Choose the Algorithm that Matches the Invariant

Use Flexbox when content forms a row or column and the primary question is how items share one axis. Toolbars, button groups, media objects, and label-value rows fit well. Use Grid when rows and columns must share track boundaries, items span regions, or placement is meaningfully two-dimensional. A page can use Grid for its shell and Flexbox inside a toolbar without either being the “better” layout system.

Do not choose solely by the visual result at one width. State the invariant: “the action group stays intrinsic while the search field consumes the remainder” describes Flexbox. “Every price aligns across cards in the same row” describes Grid. “Cards repeat until their minimum usable width no longer fits” describes an auto-fitting Grid. The invariant predicts behavior for long translations and container changes.

Container queries complement the choice because a component usually knows its containing width more reliably than the viewport width. They do not replace layout algorithms; they switch constraints when the component crosses a meaningful state. Prefer a small number of behavior-based thresholds, such as changing a two-region editor from columns to stacked rows when its minimum usable regions no longer fit.

Source order remains important for keyboard navigation and narrow layouts. Grid placement can visually rearrange items without changing reading or focus order, producing a mismatch for keyboard and assistive-technology users. Keep DOM order logical and use visual placement for modest spatial organization, not semantic reordering.

Verify Layouts with Adversarial Content

A layout test should vary the inputs that generate intrinsic constraints. Include a long unbreakable identifier, translated labels, large user-selected font sizes, missing and oversized images, empty states, validation messages, nested components, and both left-to-right and right-to-left writing directions where supported. Test browser zoom rather than only dragging a viewport.

In development tools, select the container and inspect Flexbox or Grid overlays. Record actual track sizes, gaps, line breaks, frozen flex sizes, and overflowing descendants. Disable one minimum or basis declaration at a time and state the expected effect before toggling it. This converts style editing from random search into a constraint experiment.

Automated checks should focus on invariants more stable than pixel-perfect screenshots. At representative container widths, assert that key controls remain reachable, the document has no unintended horizontal overflow, focus indicators are visible, and important regions do not overlap. Visual snapshots are useful for track changes, but allow for controlled font and platform differences or run them in a pinned browser environment.

Maintain a small boundary matrix around each responsive transition: just below, exactly at, and just above the threshold. Add content variants at those widths. Resizing only at familiar device presets can miss the narrow band where minimums and gaps no longer fit.

The operational takeaway is to debug from the outside in. Confirm the containing block’s available size, inspect item intrinsic contributions and automatic minimums, follow Flexbox free-space distribution or Grid track sizing, and assign overflow to one deliberate boundary. Once the constraints are explicit, min-width: 0, minmax(0, 1fr), flex-basis, and fr stop being charms. They become precise statements about what may shrink, what must remain usable, and where excess content is allowed to go.