Explain grid-template-columns, fr units, and minmax()

Intermediate15 min interview
Skills tested:
Reading and writing a track list with mixed unitsExplaining that fr divides leftover free space, not total widthKnowing that 1fr expands to minmax(auto, 1fr)Using minmax(0, 1fr) to prevent content-driven overflowCombining repeat(), auto, and fixed tracks in one declaration

Advertisement

🧩 Scenario

Track sizing is where Grid stops being a diagram and becomes arithmetic. A sidebar that must stay 240px while the content absorbs the rest, a three-column card row that must stay equal even when one card holds a long unbreakable string, a table-like layout whose columns size to their content up to a ceiling: each is one grid-template-columns declaration, and each goes wrong in a specific way if you treat fr as a percentage.

Architecture Walkthrough

grid-template-columns Declares the Track List

grid-template-columns takes a space-separated list of track sizes, and the number of values is the number of columns. Each value can be a length, a percentage, an fr unit, auto, an intrinsic keyword such as min-content, max-content, or fit-content(), or a minmax() function. repeat() is available to avoid writing repetitive lists, and line names can be inserted in square brackets between tracks.

grid-template-rows works identically on the block axis. Anything not covered by the explicit track list is handled by the implicit grid.

The fr Unit Divides Leftover Space

fr stands for fraction, and the key point is which quantity it fractions. It divides the free space remaining after fixed tracks, content-based tracks, and all gaps have been accounted for. It is not a percentage of the container.

Work through grid-template-columns: 200px 1fr 2fr with gap: 20px in a 1000px container. Two gaps consume 40px. The fixed track takes 200px. That leaves 760px of free space, divided in a 1:2 ratio, giving 253.33px and 506.67px. The tracks are therefore 200px, 253.33px, and 506.67px.

This is exactly why fr is preferable to percentages. repeat(3, 33.33%) with a 20px gap overflows the container, because the percentages sum to the full width and the gaps are added on top. repeat(3, 1fr) cannot overflow that way, because gaps are subtracted before the free space is divided.

1fr Is Not What Most People Think

Here is the detail that matters most in practice: 1fr is shorthand for minmax(auto, 1fr). It carries an automatic minimum, which means the track will not shrink below its content's minimum size.

For most content that is invisible. But put a long unbreakable string, a wide image, or a white-space: nowrap element in a cell and that auto minimum floors the track at the content width. The track refuses to shrink, the other fr tracks lose their share, and the grid overflows its container. Every declaration looks correct and the layout is broken.

The fix is minmax(0, 1fr), which replaces the auto minimum with zero and lets the track shrink freely. In practice, repeat(3, minmax(0, 1fr)) is the safer default for any grid whose content you do not fully control, and repeat(auto-fit, minmax(0, 1fr))-style patterns appear throughout production stylesheets for exactly this reason. Grid's minmax(0, 1fr) is the direct counterpart of Flexbox's min-width: 0.

minmax() in General

minmax(min, max) sets a floor and a ceiling for a track. The minimum accepts lengths, percentages, and the intrinsic keywords but not fr; the maximum accepts all of those plus fr.

The combinations worth knowing:

  • minmax(200px, 1fr) is the responsive workhorse: never narrower than 200px, otherwise share the free space. Paired with auto-fit it produces a responsive grid with no media queries.
  • minmax(0, 1fr) is the overflow-proof equal track.
  • minmax(min-content, max-content) is effectively what auto does for a track.
  • minmax(auto, 300px) grows with content up to a ceiling, which is what fit-content(300px) expresses more concisely.

auto Versus fr

An auto track sizes to its content, and if there is leftover free space it does receive a share, but only when no fr tracks are present to claim it. As soon as any track uses fr, the fr tracks absorb all the free space and auto tracks settle at their content size.

That distinction is what makes auto 1fr auto the right pattern for a row with content-sized ends and a flexible middle: the outer tracks take exactly what their content needs, and the middle takes everything left over.


Key Code Explained

/* Mixed track list: 1000px container, gap: 20px */
.layout {
  display: grid;
  grid-template-columns: 200px 1fr 2fr;
  gap: 20px;
}
/* gaps  = 2 x 20      = 40px
   fixed = 200px
   free  = 1000 - 40 - 200 = 760px
   1fr   = 760 / 3 x 1 = 253.33px
   2fr   = 760 / 3 x 2 = 506.67px */

/* Why fr beats percentages: this OVERFLOWS by 40px */
.percent-grid {
  display: grid;
  grid-template-columns: repeat(3, 33.33%);
  gap: 20px; /* added ON TOP of 100% of width */
}
/* This cannot: gaps are subtracted before fr divides the remainder */
.fr-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 20px;
}

/* THE OVERFLOW TRAP: 1fr means minmax(auto, 1fr) */
.cards {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
}
/* A cell containing a long unbreakable URL floors its track at the
   content width, so the track will not shrink and the grid overflows. */

/* THE FIX: replace the auto minimum with zero */
.cards-safe {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
}
/* This is Grid's equivalent of min-width: 0 on a flex item. */

/* minmax() combinations worth knowing */
.responsive {
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
}
.capped {
  grid-template-columns: minmax(auto, 300px) 1fr;
}
.same-as-capped {
  grid-template-columns: fit-content(300px) 1fr;
}

/* auto vs fr: auto tracks take content size once any fr track exists */
.toolbar-row {
  display: grid;
  grid-template-columns: auto 1fr auto; /* icon | flexible label | actions */
  align-items: center;
  gap: 12px;
}

/* Intrinsic keywords as track sizes */
.intrinsic {
  grid-template-columns: max-content min-content 1fr;
}

/* Named lines and repeat() together */
.named {
  grid-template-columns: [full-start] 1fr [content-start] minmax(0, 960px) [content-end] 1fr [full-end];
}

The .cards versus .cards-safe pair is the single most valuable thing here. The two declarations differ by one function call, and the difference decides whether a card grid survives a long URL, a code snippet, or a nowrap badge in one of its cells. Knowing that 1fr expands to minmax(auto, 1fr) is what makes that predictable instead of mysterious.

The .percent-grid versus .fr-grid comparison is the second one to keep. It is the concrete reason fr exists rather than being sugar over percentages: fr is computed after gaps are removed, so gap and track sizing compose without calc().


Tradeoffs

Track valueSizes toAbsorbs free spaceOverflow risk
200pxExactly 200pxNoOnly if the sum exceeds the container
33.33%Percentage of containerNoYes, gaps add on top
1frminmax(auto, 1fr)YesYes, content sets a floor
minmax(0, 1fr)Free space share, no floorYesNo
autoContent, plus free space only if no fr existsConditionallyLow
min-contentNarrowest without internal overflowNoNo
max-contentWidest, no wrappingNoYes
fit-content(300px)Content up to 300pxNoNo

What Interviewers Actually Check

  • Whether you can compute track widths from a mixed list including gaps
  • Whether you explain fr as dividing leftover space rather than as a percentage
  • Whether you know 1fr expands to minmax(auto, 1fr) and what that implies
  • Whether you reach for minmax(0, 1fr) when content is untrusted
  • Whether you can distinguish an auto track from an fr track when free space exists

Follow-Up Questions

  1. What happens if the fr tracks' total minimum content size exceeds the container even with minmax(0, 1fr)?
  2. How does fit-content() differ from minmax(auto, <length>) in edge cases?
  3. Can fr be used as the minimum argument of minmax(), and why not?
  4. How do min-content and max-content tracks behave differently for a cell containing a long paragraph?
  5. What does grid-template-columns: subgrid do, and what problem does it solve that minmax() cannot?

Common Candidate Mistakes

  • Describing 1fr in a three-column grid as equivalent to 33.33%, which is only true when there is no gap and no fixed or content-sized track in the list
  • Not knowing 1fr carries an auto minimum, so a long unbreakable string in one cell floors that track and overflows the whole grid
  • Assuming gap is added on top of fr tracks and can cause overflow, when gaps are subtracted from the container before free space is divided
  • Reaching for percentage tracks and then adding calc() to compensate for gaps, work that fr does automatically
  • Treating auto and 1fr as interchangeable, when auto yields the free space to any fr track present and settles at its content size

Interview Readiness Checklist

Before you leave this question, make sure you can answer:

  • Can you compute track widths for a mixed track list including gaps?
  • Can you explain what fr divides and why it is not a percentage?
  • Can you explain what 1fr expands to and why that causes overflow with wide content?
  • Can you write minmax() using intrinsic keywords as either argument?
  • Can you explain the difference between an auto track and a 1fr track when free space exists?

Summary

grid-template-columns declares a track list, where each value can be a length, a percentage, an fr unit, auto, an intrinsic keyword, or a minmax() function, and repeat() shortens repetitive lists. The number of values determines the number of explicit columns.

The fr unit divides the free space remaining after fixed tracks, content-sized tracks, and all gaps are accounted for. That is why 200px 1fr 2fr with a 20px gap in a 1000px container yields 200px, 253.33px, and 506.67px, and why fr is safer than percentages: repeat(3, 33.33%) plus a gap overflows because the percentages already claim the full width, while fr cannot overflow for that reason.

The detail that causes real bugs is that 1fr is shorthand for minmax(auto, 1fr). The auto minimum floors the track at its content's minimum size, so a long unbreakable string or a wide image in one cell prevents that track from shrinking and pushes the grid past its container. minmax(0, 1fr) removes the floor and is the Grid counterpart to min-width: 0 on a flex item. Beyond that, minmax(200px, 1fr) is the responsive workhorse that pairs with auto-fit, fit-content() caps a content-sized track, and auto tracks take their content size as soon as any fr track is present to claim the leftover space.

Frequently Asked Questions

Why does 1fr overflow when the content is wide?

Because 1fr means minmax(auto, 1fr), and the auto minimum floors the track at its content size. minmax(0, 1fr) removes that floor.

Does fr account for gap?

Yes. Gaps are subtracted from the container size before the free space is divided among fr tracks, so gaps never cause overflow on their own.

Advertisement


Stay Updated

Get the latest frontend challenges, interview questions and tutorials delivered to your inbox.

Advertisement