Understanding the fr Unit in CSS Grid Layout
The CSS Grid fr unit distributes remaining space after fixed tracks and gaps are accounted for, not a percentage of total width. Learn how available space is calculated and when content minimums override fr sizing.
The CSS Grid fr unit kills the percentage-width hack you have been carrying since 2014. The old pattern: three columns, each width: calc(33.333% - 1rem), a 1.5rem gap, and a wrapper that broke the moment you changed the viewport or added a fourth column. The fr unit does not work that way. It does not measure fractions of the box. It measures fractions of the remaining space after every fixed track and every gap has already been subtracted. That one difference is why an fr track next to a fixed track lands at a different width than a percentage would, and why the CSS Grid fr unit belongs in your grid-template-columns declaration by default.
The Failure The Fr Unit Replaces
Before you write another grid, see the failure. Take a wrapper that is 1000px wide, with a 200px sidebar and a 1.5rem gap. A percentage layout gives the main column 800px minus the gap, computed as calc(100% - 200px - 1.5rem). That works until the wrapper narrows. At 400px, the sidebar swallows the main column and the page overflows. The fr unit takes a different route. The track list grid-template-columns: 200px 1fr computes the 200px track first, subtracts the 1.5rem gap, and hands the remainder to the 1fr track. The sidebar never shrinks, the main column never overflows, and you never write calc() with a percentage inside a grid again.
.container {
display: grid;
grid-template-columns: 200px 1fr;
gap: 1.5rem;
}
The old percentage approach had a second failure mode: it distributed space based on the total wrapper width, not on what was left. With a sidebar and two main columns, you wrote calc(33.333% - 1rem) twice and discovered the two main columns were not equal because the sidebar was eating into the percentage base. The fr unit avoids that by operating on the free space. The specification defines fr as a fraction of the free space, which is the available space minus the sum of all non-flex track sizes. The word free space is the key. You are not dividing the box. You are dividing what remains after the fixed tracks take their share. That is why the fr unit is the first distribution mechanism that matches how a human thinks about a page: sidebar first, then everything else.
Flex Factors And Proportional Distribution
The phrase fractional unit grid distribution sounds like it means equal slices. It does not, and the difference shows the moment you give two fr tracks different flex factor values. A track list of 1fr 2fr does not mean the second column is twice as wide as the first in every case. It means the remaining space is divided into three parts, and the second track receives two of them. That is proportional distribution. The flex factor is the multiplier in the division.
.grid-two-to-one {
display: grid;
grid-template-columns: 1fr 2fr;
gap: 1rem;
}
In a 900px wrapper with a 1rem gap, the free space is 884px. The first track gets roughly 295px, the second gets roughly 589px. Nothing about that is surprising, but it is the first place beginners expect equality and find a 2:1 ratio instead. The flex factor is a weight, not a promise of equal width.
The Auto Minimum Size Problem
Now the question that separates a working grid from a broken one: what is the smallest size of an fr track? The claim you hear everywhere is that 1fr means equal distribution. The real behaviour is that a 1fr track has a default smallest size of auto, and auto means the content-based floor. A long word, an image with an intrinsic width, or a pre-formatted code block can force a 1fr track wider than the free space would suggest. The specification, CSS Grid Layout Module Level 1, section 6.6, defines this as the automatic minimum size, and it is the single most common source of grid overflow. The override is explicit: minmax(0, 1fr) sets the floor to zero, so the track can shrink to whatever the free space allows, regardless of content.
.grid-no-blowout {
display: grid;
grid-template-columns: minmax(0, 1fr) 200px;
}
Use this when you have untrusted content, a long URL, or a user-generated comment that should truncate instead of expanding the track.
Fr Versus Percentage: The Concrete Test
Run this test. Build a grid with grid-template-columns: 200px 1fr and another with grid-template-columns: 200px calc(100% - 200px). In a 1000px wrapper, both produce the same result. Narrow the wrapper to 500px. The percentage version computes calc(100% - 200px) as 300px, which is correct. Now add a third column: 150px 200px 1fr versus 150px 200px calc(100% - 350px). At 600px, the percentage version gives 250px. At 400px it gives 50px, squeezing the main column to nothing. The fr version gives roughly 234px at 600px and roughly 34px at 400px, but the key difference is what happens when the available space drops below the sum of fixed tracks. The percentage version overflows. The fr version produces a zero-width track and the grid wrapper scrolls or clips without breaking the layout. Percentage is a calculation against the box. fr is a calculation against the remainder.
That remainder calculation is the heart of CSS Grid track sizing. The track list defines two kinds of tracks: fixed tracks and flexible tracks. Fixed tracks use px, rem, em, or percentage, and they are subtracted from the available space first. Flexible tracks use the fr unit, and they compete for the remaining space according to their flex factor. The order of the tracks in the list does not affect the calculation. What matters is the sum of fixed sizes and the sum of flex factors. Write 2fr 1fr and the first track gets twice the remaining space of the second, regardless of which one appears first. The browser does not honour the sequence. It honours the weights.
Using Minmax Correctly
The most common mistake with minmax and fr is writing minmax(1fr, 2fr), which is invalid. The fr unit cannot appear in both the smallest and largest position when the floor is a flex factor. The valid forms are minmax(0, 1fr), minmax(auto, 1fr), and minmax(200px, 1fr). The difference between minmax(auto, 1fr) and minmax(0, 1fr) is exactly the auto floor. The first allows content to expand the track. The second forces the track to shrink to zero if the free space demands it.
.grid-safe {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
}
This is the pattern for a two-column card layout where either side can contain a long word. Without the zero floor, a single unbroken string in the left card pushes the right card past the wrapper edge.
The auto floor rule is not a bug and it is not a browser quirk. It is a deliberate choice in the CSS Grid Layout specification to prevent content from being unreachable. If a track could shrink below its content’s floor, the content would overflow invisibly or become unscrollable. The specification authors decided that the default should favour content visibility over pixel-perfect equal distribution. That is why the override exists. When you write minmax(0, 1fr), you are telling the browser that you accept the risk of clipping or scrolling, and you want the track to obey the free space calculation instead of the content. This is the most important single decision you will make when building a grid that contains user-generated content, images without explicit dimensions, or text in an unknown language.
There is a deeper question that every grid author runs into eventually: what happens when the available space is zero or negative? The specification handles this by saying that the flex factor distribution only applies to the free space. If the fixed tracks alone exceed the wrapper, the flexible tracks receive zero. The browser does not underflow. It does not create negative tracks. It gives the fr tracks a size of zero and lets the fixed tracks overflow. This is the exact behaviour that makes the fr unit safe in responsive layouts. A percentage layout with a fixed sidebar and a calc() main column will attempt to compute a negative main column, which browsers resolve by clamping to zero, but the result is a broken overlap. The fr unit shrinks the flexible track to nothing and lets the fixed sidebar stay intact. The page is broken, but it is broken predictably, and that predictability is what lets you debug it.
Applying Fr To Rows
The grid-template-rows declaration uses the same rules, and the same auto floor applies. A row with 1fr will grow to fit its content if the content has a larger intrinsic size than the free space allows. This is the classic problem with a grid row that contains a large image: the row expands, the other rows shrink, and the layout no longer matches the fractions you wrote. The fix is the same: minmax(0, 1fr) on the row track.
.grid-rows {
display: grid;
grid-template-rows: minmax(0, 1fr) 200px minmax(0, 1fr);
}
This declaration makes the middle row a fixed 200px and lets the top and bottom rows share the remaining height equally, without the top row blowing out because it contains a 600px image.
Practical Guidance
Now that you have the mechanics, here is the practical guidance. Use fr for the main content area of a page, the flexible column in a sidebar layout, and the rows of a card grid. Do not use fr for a track that must never shrink, like a rail or a fixed navigation. For that, use a fixed track. Do not use fr for a track that contains a long word and must truncate, unless you also use minmax(0, 1fr). Do not use fr in a wrapper that has no defined width, because the available space is the wrapper's content box, and if that box is auto, the fr tracks resolve to zero. This last point confuses most developers: an fr track inside a block-level grid wrapper with no explicit width on the wrapper will collapse. The available space is the wrapper's own content width, which is determined by the tracks themselves, creating a circular dependency that the specification resolves by treating the free space as zero.
The explicit grid is the track list you write, and it is the only place the fr unit has meaning. You cannot use fr in a width property, a margin, a padding, or a flex-basis. The fr unit is a track-sizing value and nothing else. If you try grid-template-columns: 1fr 1fr and then width: 1fr on a child, the browser ignores the width declaration because fr is not a valid length unit outside a track context. This is the most common misuse of the unit, and it produces no error, just a silent fallback to auto. The result is a grid track that expands to fit its content, and the layout you intended disappears.
The fr unit also behaves differently from a percentage in one more way that matters for accessibility and print. A percentage track is resolved against the wrapper width at the time of layout. An fr track is resolved against the free space, which is the wrapper width minus fixed tracks and gaps. With a fixed sidebar and a 1fr main column, the main column is exactly the wrapper width minus the sidebar minus the gap. A percentage main column would be a fraction of the wrapper width, which means the main column and the sidebar never sum to the wrapper width. This is not a rounding issue. It is a structural difference. The fr unit guarantees that the sum of all tracks plus the sum of all gaps equals the wrapper width. Percentage tracks do not guarantee that, because a percentage of the wrapper width plus a fixed pixel value does not necessarily equal the wrapper width. This is the reason the fr unit is the correct tool for any layout where you want the tracks to fill the wrapper exactly.
Fr Versus Percentage: The Comparison Table
Here is the table that shows the difference on three axes: what the track measures, what the floor is, and what happens when the wrapper shrinks. This is the comparison you will use when someone asks why fr is better than percentage.
| Track type | Measures against | Default floor | Shrink behaviour |
|---|---|---|---|
| percentage | Wrapper width | Auto (content-based) | Track can overflow wrapper |
| fr | Free space after fixed tracks | Auto (content-based) | Track shrinks to zero if free space is zero |
| fixed (px) | Absolute length | Fixed value | Never shrinks |
That table is the whole story. The fr track is the only one that both respects the fixed tracks and shrinks to zero when the wrapper runs out of space.
Debugging The 1Am Overflow
Now the failure case, the one that happens at 1am when you are debugging a grid that suddenly overflows. You wrote grid-template-columns: 1fr 1fr, and the right column is wider than the left. Check the content. The right column contains a long URL or an image with width: 100% and a large intrinsic size. The auto floor has expanded the right track. The fix is minmax(0, 1fr) on both tracks. If that does not solve it, the wrapper itself has no explicit width, and the free space is zero, so the tracks are resolving to their content-based floors. Give the wrapper a width, or use an inline-size constraint. The third failure case looks like a grid bug but is not: you have a gap larger than the wrapper, and the fr tracks resolve to zero, leaving only the gap visible. Reduce the gap or the fixed tracks.
The fr unit is not a magic bullet. It is the right choice when you want proportional distribution of the remaining space, and you are willing to control the floor with minmax(0, 1fr) when content demands it. It is the wrong choice when you need a track to keep a smallest width regardless of content, in which case a fixed track with a max-width fallback is simpler. It is also the wrong choice when you need to align tracks across multiple grid wrappers, because each wrapper computes its own free space independently. For that, you need subgrid, which is a different feature entirely. The fr unit is a single-wrapper tool.
Who Should Use The Fr Unit
Use the fr unit if you maintain a legacy codebase and want to replace a calc() percentage layout with a modern grid. Use it if you need to explain why a grid column behaves the way it does. Use it if you want to understand why a layout exported from a tool ships with 1fr instead of a percentage. Do not use it if you need a track that never shrinks below a content-based floor and you are unwilling to add minmax(0, 1fr). Do not use it for a one-dimensional layout where flexbox is the simpler tool. The fr unit is for two-axis layouts. Forcing it into a single axis is a misuse. Flexbox handles that case with less ceremony and fewer surprises.
Here is the answer the whole page has been building toward, and it is the one sentence that would not appear on a competitor’s page: the fr unit distributes free space, not wrapper width, and the auto floor is the specification’s deliberate choice to favour content visibility over equal distribution, so minmax(0, 1fr) is not an optimisation but the explicit override of a rule that will otherwise expand your track to fit a 60-character unbroken string.
FAQ
Does 1fr always mean one equal share of the wrapper?
No. 1fr means one share of the free space, which is the wrapper width minus fixed tracks and gaps. With a 200px sidebar and a 1fr main column, the main column is the remaining width, not half the wrapper.
Why does my 1fr track grow wider than the free space?
Because the auto floor is content-based. A long word or an image with an intrinsic width expands the track. Use minmax(0, 1fr) to force the track to shrink to the free space.
Can I use fr in width or height properties?
No. The fr unit is only valid inside a track list in grid-template-columns or grid-template-rows. Outside a track context, the browser ignores it.
What is the difference between minmax(auto, 1fr) and minmax(0, 1fr)?
minmax(auto, 1fr) allows the content-based floor to expand the track. minmax(0, 1fr) forces the floor to zero, so the track can shrink to the free space.
When do fr tracks resolve to zero?
When the sum of fixed tracks and gaps equals or exceeds the wrapper width. The browser gives the fr tracks zero width rather than creating a negative track.