CSS Module 11 – CSS Grid
Responsive grids: minmax(), auto-fit, auto-fill
Build card grids that choose their own column count with repeat(), minmax(), auto-fill and auto-fit, and stop long links from blowing out a grid on phones.
What you will learn
- Build card grids that choose their own column count with repeat(auto-fit, minmax(...))
- Explain the difference between auto-fill and auto-fit
- Prevent grid blowouts from long content with minmax(0, 1fr) and min()
Before you start
What you’ll learn · Before you start
What you will learn
- Build card grids that choose their own column count with repeat(auto-fit, minmax(...))
- Explain the difference between auto-fill and auto-fit
- Prevent grid blowouts from long content with minmax(0, 1fr) and min()
Before you start
On this page
One line of CSS gives a card grid that picks its own number of columns:
grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr)). It asks for as many columns of at least 16rem as fit
in the grid, then shares the leftover space between them, so a phone gets one column and a wide screen five, with no
media query. auto-fit does the same but collapses the columns that stay empty, so a few cards stretch to fill the
row instead of keeping their size. Two details turn this into a dependable pattern: min(16rem, 100%) stops a single
column overflowing a very narrow screen, and minmax(0, 1fr) stops a long link or code sample making one column wider
than the others. Both fixes and both failures appear below as numbers from Chromium 153, Firefox 155 and WebKit 26.6.
minmax(): a range for each track
minmax(min, max) gives a track a smallest and a largest size. minmax(200px, 1fr) is “at least 200 px, and an equal
share of the leftover space beyond that”; minmax(200px, 300px) grows from 200 to 300 px and stops. If the maximum is
smaller than the minimum, the maximum is ignored and the track is the minimum. Both arguments accept lengths,
percentages, auto, min-content and max-content, and only the maximum may be an fr value.
One equivalence explains a lot of surprises: a bare 1fr means minmax(auto, 1fr). Its minimum is auto, which for
grid items usually means the content’s min-content width, so a 1fr column is never narrower than its widest word,
image or table. That is what makes the blowout further down possible.
repeat() with auto-fill: as many columns as fit
repeat(auto-fill, <track>) repeats the track as many times as fits in the grid’s width with the gaps between the
copies, and at least once. To count, the browser uses the track’s maximum when that is a length, and its minimum when
the maximum is flexible: for minmax(16rem, 1fr) it counts 16rem columns. Then the copies are laid out normally, and
1fr shares out what is left. The rule as a program, for a minimum of 16rem (256 px) and 16 px gaps:
// How many columns repeat(auto-fill, minmax(<min>, 1fr)) makes, and how wide they are (CSS Grid Level 2,
// "Repeat-to-fill"): as many <min>-wide columns as fit with their gaps, at least one; then 1fr shares the rest.
// min(16rem, 100%) lowers the minimum to the grid's own width, so a single column can never overflow.
const rem = 16;
const gap = 16;
function autoFill(width, minimum) {
const count = Math.max(1, Math.floor((width + gap) / (minimum + gap)));
const column = Math.max(minimum, (width - gap * (count - 1)) / count);
return { count, column, overflow: Math.max(0, count * column + gap * (count - 1) - width) };
}
const round = (n) => Math.round(n * 100) / 100;
console.log('grid width minmax(16rem, 1fr) minmax(min(16rem, 100%), 1fr)');
for (const width of [248, 328, 600, 900, 1200, 1408]) {
const plain = autoFill(width, 16 * rem);
const clamped = autoFill(width, Math.min(16 * rem, width));
const show = (r) => `${r.count} × ${round(r.column)}px${r.overflow ? `, ${round(r.overflow)}px too wide` : ''}`.padEnd(30);
console.log(`${String(width).padStart(6)}px ${show(plain)}${show(clamped)}`.trimEnd());
} Output
grid width minmax(16rem, 1fr) minmax(min(16rem, 100%), 1fr) 248px 1 × 256px, 8px too wide 1 × 248px 328px 1 × 328px 1 × 328px 600px 2 × 292px 2 × 292px 900px 3 × 289.33px 3 × 289.33px 1200px 4 × 288px 4 × 288px 1408px 5 × 268.8px 5 × 268.8px
Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node columns.mjs
Runs on this device, in your browser. The first run downloads JavaScript (about 0.6 MB), which is kept for the next runs.
Your run, in this browser
The second column of the output is the narrow-screen fix covered below. The counting rule has a consequence that catches people out. With a length as the maximum, that length decides the count:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Which size decides the number of columns?</title>
<style>
body { margin: 0; font: 16px/24px Arial, sans-serif; }
.cards { display: grid; inline-size: 1000px; margin-block-end: 16px; }
.cards > * { background: #dde; }
#flexible-max { grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); }
#fixed-max { grid-template-columns: repeat(auto-fill, minmax(200px, 300px)); }
</style>
</head>
<body>
<div class="cards" id="flexible-max"><div>1</div><div>2</div><div>3</div><div>4</div><div>5</div><div>6</div></div>
<div class="cards" id="fixed-max"><div>1</div><div>2</div><div>3</div><div>4</div><div>5</div><div>6</div></div>
<script>
for (const grid of document.querySelectorAll('.cards')) {
const tracks = getComputedStyle(grid).gridTemplateColumns;
const right = Math.max(...[...grid.children].map((c) => c.getBoundingClientRect().right));
console.log(`${grid.id}: ${tracks.split(' ').length} columns (${tracks}); the cards end at ${right}px of 1000px`);
}
</script>
</body>
</html> | Track | Columns in 1000 px | Where the cards end |
|---|---|---|
minmax(200px, 1fr) |
5 × 200 px | 1000 px |
minmax(200px, 300px) |
3 × 300 px | 900 px, 100 px empty |
All three engines agreed. The second grid has room for five 200 px columns, but it counted 300 px columns, fitted
three, and could not grow them past 300 px, so 100 px stayed empty at the end. If you want the minimum to decide, keep
the maximum flexible (1fr) and limit the card’s width in another way, for example with max-inline-size on the
card itself.
auto-fill or auto-fit
auto-fit counts exactly like auto-fill. The difference comes after the items are placed: auto-fit collapses
every repeated track that holds no item to 0 px, together with its gaps, so the tracks that do hold items share the
whole row. The same 1200 px grid with 2, 5 and 12 cards:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>auto-fill and auto-fit with 2, 5 and 12 cards</title>
<style>
body { margin: 0; font: 16px/24px Arial, sans-serif; }
.cards { display: grid; inline-size: 1200px; gap: 16px; margin-block-end: 16px; }
.cards > * { background: #dde; }
.fill { grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); }
.fit { grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); }
</style>
</head>
<body>
<div class="cards fill" data-cards="2"></div>
<div class="cards fit" data-cards="2"></div>
<div class="cards fill" data-cards="5"></div>
<div class="cards fit" data-cards="5"></div>
<div class="cards fill" data-cards="12"></div>
<div class="cards fit" data-cards="12"></div>
<script>
// Fill each grid with its number of cards, then print its tracks and the width of its first card.
for (const grid of document.querySelectorAll('.cards')) {
for (let i = 1; i <= Number(grid.dataset.cards); i++) grid.insertAdjacentHTML('beforeend', `<div>Card ${i}</div>`);
}
for (const grid of document.querySelectorAll('.cards')) {
const tracks = getComputedStyle(grid).gridTemplateColumns;
const width = grid.firstElementChild.getBoundingClientRect().width;
console.log(`${grid.classList[1]}, ${grid.dataset.cards} cards: ${tracks.split(' ').length} tracks (${tracks}); a card is ${Math.round(width * 100) / 100}px wide`);
}
</script>
</body>
</html> auto-fill and auto-fit with two cards, drawn to scale
Text description of the diagram
Two rows, drawn to scale, show a grid 1200 px wide with 16 px gaps and the track list repeat(…, minmax(200px, 1fr)), holding only two cards.
- The first row is auto-fill. Five tracks fit, and all five are kept: card 1 and card 2 fill the first two tracks, each 227.2 px wide, and the last three tracks are drawn as dashed, empty boxes.
- The second row is auto-fit. The three empty tracks collapse to 0 px together with their gaps, so the two cards share the whole width and each is 592 px wide.
- A note under the rows says that with five or more cards both keywords give the same five columns: the difference only shows when some tracks would be empty.
| Cards | auto-fill cards |
auto-fit cards |
|---|---|---|
| 2 cards | 227.2 px | 592 px |
| 5 cards | 227.2 px | 227.2 px |
| 12 cards | 227.2 px | 227.2 px |
With auto-fill there were five 227.2 px tracks every time. With two cards, auto-fit made the same five and
collapsed the three empty ones: getComputedStyle() listed them as 592px 592px 0px 0px 0px in all three engines.
Chromium and WebKit reported 227.188 or 227.203 px where Firefox reported 227.2, the same width rounded to each
engine’s fraction of a pixel. The choice follows from the table:
auto-fillkeeps cards the same size whether there are 2 or 200 of them. Use it for product lists, search results and galleries, where a single result stretched across the screen looks broken.auto-fitlets a few items share the row. Use it for a fixed, small set, such as three feature panels that should fill the width on a large screen and stack on a phone.
Grid blowouts: when 1fr columns are not equal
Three 1fr columns are equal only while their content allows it. A product row on a 360 px phone, with a long link in
the middle cell, in five versions:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>A long link in a three-column grid</title>
<style>
body { margin: 0; font: 16px/24px Arial, sans-serif; }
.row { display: grid; inline-size: 360px; gap: 12px; margin-block-end: 16px; }
.row > * { background: #dde; }
#plain { grid-template-columns: repeat(3, 1fr); }
#zero-min { grid-template-columns: repeat(3, minmax(0, 1fr)); }
#break-word { grid-template-columns: repeat(3, 1fr); overflow-wrap: break-word; }
#anywhere { grid-template-columns: repeat(3, 1fr); overflow-wrap: anywhere; }
#zero-anywhere { grid-template-columns: repeat(3, minmax(0, 1fr)); overflow-wrap: anywhere; }
</style>
</head>
<body>
<div class="row" id="plain"><div>Kurta</div><div>shop.example/item?ref=WA_BROADCAST_DIWALI_OFFER_7Q2K9</div><div>₹899</div></div>
<div class="row" id="zero-min"><div>Kurta</div><div>shop.example/item?ref=WA_BROADCAST_DIWALI_OFFER_7Q2K9</div><div>₹899</div></div>
<div class="row" id="break-word"><div>Kurta</div><div>shop.example/item?ref=WA_BROADCAST_DIWALI_OFFER_7Q2K9</div><div>₹899</div></div>
<div class="row" id="anywhere"><div>Kurta</div><div>shop.example/item?ref=WA_BROADCAST_DIWALI_OFFER_7Q2K9</div><div>₹899</div></div>
<div class="row" id="zero-anywhere"><div>Kurta</div><div>shop.example/item?ref=WA_BROADCAST_DIWALI_OFFER_7Q2K9</div><div>₹899</div></div>
<script>
// The column widths, how far the grid reaches, and whether the link's text spills out of its own cell.
const round = (n) => Math.round(n * 100) / 100;
for (const row of document.querySelectorAll('.row')) {
const link = row.children[1];
const reach = round(Math.max(...[...row.children].map((c) => c.getBoundingClientRect().right)));
const spill = link.scrollWidth > link.clientWidth ? `the text spills ${link.scrollWidth - link.clientWidth}px out of its cell` : 'the text stays in its cell';
console.log(`${row.id}: columns ${getComputedStyle(row).gridTemplateColumns}; the grid reaches ${reach}px of 360px; ${spill}`);
}
</script>
</body>
</html> | Columns | Widths, px | The link |
|---|---|---|
1fr |
38 · 347 · 35, 444 in all | fits its wide column |
minmax(0, 1fr) |
112 · 112 · 112 | spills 235 px out |
1fr, break-word |
38 · 347 · 35, 444 in all | fits its wide column |
1fr, anywhere |
112 · 112 · 112 | wraps in its cell |
minmax(0, 1fr), anywhere |
112 · 112 · 112 | wraps in its cell |
The first column shows each version’s track size and, where there is one, its overflow-wrap value. The widths are
Chromium’s, rounded to whole pixels (38.25, 346.92 and 35.02 px before rounding); Firefox and WebKit differed only in
the second decimal. The first row is the blowout: the
link has no place to break, so its min-content width is the whole link, and the automatic minimum of 1fr made its
column 347 px wide. The other columns shrank to their own minimums, the width of the product name and of its price,
and the row still ran 84 px past the screen. The fixes work at different levels:
minmax(0, 1fr)removes the automatic minimum, so the columns are equal whatever the content. The link then overflows its own cell, which is still better than a page that scrolls sideways, but it needs a second fix.overflow-wrap: anywherelets the browser break an unbreakable string at any character, and, unlikebreak-word, it counts those breaks when it works out the min-content width. That is whybreak-wordchanged nothing here andanywherefixed the columns on its own.- Both together are the robust choice for grids that show text you do not control: equal columns, and long
strings that wrap inside them.
min-inline-size: 0on the items does the same job asminmax(0, 1fr).
Links, order numbers, email addresses, code and wide tables cause most blowouts. Images do too, unless they have
max-inline-size: 100%: a percentage maximum lets an image’s min-content width count as zero.
Narrow screens: min() inside minmax()
A grid column never goes below its minimum, so a minimum wider than the screen overflows it, even with a single column. WCAG 2.2 success criterion 1.4.10 (Reflow) asks pages to work at a width of 320 CSS pixels without scrolling sideways, which is what a 1280 px window shows at 400 % zoom. Cards of at least 20rem (320 px), on screens 320 and 280 px wide with 16 px of padding on each side:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>A card grid on narrow screens</title>
<style>
body { margin: 0; padding: 0 16px; font: 16px/24px Arial, sans-serif; }
.cards { display: grid; gap: 16px; margin-block-end: 16px; }
.cards > * { background: #dde; }
#plain { grid-template-columns: repeat(auto-fill, minmax(20rem, 1fr)); }
#clamped { grid-template-columns: repeat(auto-fill, minmax(min(20rem, 100%), 1fr)); }
</style>
</head>
<body>
<div class="cards" id="plain"><div>Card 1</div><div>Card 2</div></div>
<div class="cards" id="clamped"><div>Card 1</div><div>Card 2</div></div>
<script>
for (const grid of document.querySelectorAll('.cards')) {
const r = grid.firstElementChild.getBoundingClientRect();
console.log(`${grid.id}: the grid has ${grid.clientWidth}px; a card runs from ${r.left}px to ${r.right}px`);
}
console.log(`the page scrolls sideways by ${document.documentElement.scrollWidth - document.documentElement.clientWidth}px`);
</script>
</body>
</html> | Minimum | 320 px screen | 280 px screen |
|---|---|---|
20rem |
card 320 px, page scrolls 16 px | card 320 px, page scrolls 56 px |
min(20rem, 100%) |
card 288 px, fits | card 248 px, fits |
All three engines agreed. After the padding, the grid had 288 px on the 320 px screen and 248 px on the 280 px one;
the 20rem card ran from 16 to 336 px on both, past the edge of the screen. Inside a track size, a percentage is a
share of the grid’s width, so min(20rem, 100%) is 20rem on every screen that has room for it and the grid’s own
width on any screen that does not.
Tracks sized by their content
Some columns should follow their content rather than share space: labels next to values, a column of buttons, tags. Three keywords and one function do that:
max-contentis the content’s width on one line, however long.min-contentis its narrowest width, wrapping at every opportunity: the longest word decides.fit-content(<limit>)is the content’s width, but never more than the limit, beyond which the text wraps.autobehaves likemax-contentas a maximum and the content’s minimum as a minimum, and stretches when there is spare space.
An order summary as a definition list, 600 px wide, whose last label is a 52-character tax-number label:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Label columns sized by their content</title>
<style>
body { margin: 0; font: 16px/24px Arial, sans-serif; }
dl { display: grid; inline-size: 600px; column-gap: 16px; row-gap: 8px; margin: 0 0 24px; }
dt, dd { margin: 0; background: #dde; }
#max-content { grid-template-columns: max-content 1fr; }
#min-content { grid-template-columns: min-content 1fr; }
#fit-content { grid-template-columns: fit-content(12rem) 1fr; }
</style>
</head>
<body>
<dl id="max-content">
<dt>Name</dt><dd>Asha Rao</dd>
<dt>Delivery address</dt><dd>14 MG Road, Bengaluru 560001</dd>
<dt>GSTIN (Goods and Services Tax Identification Number)</dt><dd>29ABCDE1234F1Z5</dd>
</dl>
<dl id="min-content">
<dt>Name</dt><dd>Asha Rao</dd>
<dt>Delivery address</dt><dd>14 MG Road, Bengaluru 560001</dd>
<dt>GSTIN (Goods and Services Tax Identification Number)</dt><dd>29ABCDE1234F1Z5</dd>
</dl>
<dl id="fit-content">
<dt>Name</dt><dd>Asha Rao</dd>
<dt>Delivery address</dt><dd>14 MG Road, Bengaluru 560001</dd>
<dt>GSTIN (Goods and Services Tax Identification Number)</dt><dd>29ABCDE1234F1Z5</dd>
</dl>
<script>
for (const list of document.querySelectorAll('dl')) {
const last = list.querySelector('dt:last-of-type').getBoundingClientRect();
console.log(`${list.id}: columns ${getComputedStyle(list).gridTemplateColumns}; the long label is ${last.height / 24} line(s) tall`);
}
</script>
</body>
</html> | Label column | Widths, px | Long label |
|---|---|---|
max-content |
393 · 191 | 1 line |
min-content |
90 · 494 | 6 lines |
fit-content(12rem) |
192 · 392 | 3 lines |
The widths are the label column’s and the value column’s, rounded; Chromium measured 392.77 px for the max-content
labels, Firefox 393.12 px and WebKit 393.05 px. max-content
gave the long label its whole line and squeezed the values; min-content made the column as narrow as its longest
word, “Identification”, and broke the long label over six lines; fit-content(12rem) let short labels keep their size
and capped the column at 192 px, so the long label wrapped over three lines. For forms and summaries, fit-content()
with a sensible limit is usually what you want.
Long labels on Indian forms
The long label in the example is for a GSTIN, the GST registration number that businesses in India write on their tax
invoices, so Indian invoice and billing forms often have a field for it. Labels like it, and their translations into
Indian languages, are where a max-content label column hurts most: size such columns with fit-content() and test
them with the longest label in every language the form supports.
Practise: predict a product grid
Exercise · Medium · JavaScript
Predict the columns of an auto-fill or auto-fit grid
A product grid uses grid-template-columns: repeat(auto-fill, minmax(<min>, <max>)), or auto-fit in its place, with a gap between the columns. Write three functions in columns.mjs and export them, so that they predict what a browser draws. All lengths are in px, and max is a number of px or the string "1fr".
The first, repeatCount(width, track, gap), returns how many columns the repeat makes in a grid width px wide, where track is { min, max }. A browser counts with the maximum when it is a length (but never less than the minimum), and with the minimum when the maximum is 1fr. It makes as many columns as fit together with the gaps between them, and never fewer than one.
The second, columnWidths(width, { min, max, gap, items, fit }), returns the width of every column, in order. With fit false (auto-fill), every column is kept. With fit true (auto-fit), the columns that would stay empty, all those after the first items, collapse to 0 together with their gaps, and the others share the space. A 1fr column takes an equal share of the space the gaps leave, but never less than min; a column with a length as its maximum gets an equal share, but never less than min and never more than max.
The third, safeMinimum(min, width), returns the minimum that minmax(min(<min>, 100%), …) gives in a grid width px wide.
The sample tests import the three functions from columns.mjs and run in your browser. They accept answers within 0.01 px.
Starter code · columns.mjs
/** How many columns repeat(auto-fill, minmax(min, max)) makes in a grid `width` px wide. */
export function repeatCount(width, track, gap) {
// Replace this line with your code.
return 1;
}
/** The width of every column of the repeat, in order. */
export function columnWidths(width, { min, max, gap, items, fit }) {
// Replace this line with your code.
return [width];
}
/** The minimum that minmax(min(<min>, 100%), …) gives in a grid `width` px wide. */
export function safeMinimum(min, width) {
// Replace this line with your code.
return min;
} The sample tests · columns.test.mjs
import { test, assert } from 'mysmartcopilot:test';
import { repeatCount, columnWidths, safeMinimum } from './columns.mjs';
const near = (actual, expected, what) => {
assert.equal(actual.length, expected.length, `${what}: ${expected.length} columns expected, got ${actual.length}`);
expected.forEach((e, i) => assert.ok(Math.abs(actual[i] - e) < 0.01, `${what}: column ${i + 1} should be ${e}, got ${actual[i]}`));
};
const card = { min: 256, max: '1fr', gap: 16, items: 12, fit: false };
test('as many columns as fit, never fewer than one', () => {
assert.equal(repeatCount(1200, { min: 200, max: '1fr' }, 16), 5);
assert.equal(repeatCount(1000, { min: 200, max: '1fr' }, 0), 5);
assert.equal(repeatCount(100, { min: 200, max: '1fr' }, 16), 1);
});
test('the gaps count only between columns', () => {
assert.equal(repeatCount(632, { min: 200, max: '1fr' }, 16), 3);
assert.equal(repeatCount(631, { min: 200, max: '1fr' }, 16), 2);
});
test('a length as the maximum decides the count', () => {
assert.equal(repeatCount(1000, { min: 200, max: 300 }, 0), 3);
assert.equal(repeatCount(1000, { min: 400, max: 300 }, 0), 2);
});
test('a card grid at six widths', () => {
near(columnWidths(328, card), [328], '328px');
near(columnWidths(600, card), [292, 292], '600px');
near(columnWidths(900, card), [289.3333, 289.3333, 289.3333], '900px');
near(columnWidths(1200, card), [288, 288, 288, 288], '1200px');
near(columnWidths(1408, card), [268.8, 268.8, 268.8, 268.8, 268.8], '1408px');
near(columnWidths(248, card), [256], '248px: the one column is wider than the grid');
});
test('auto-fit collapses the empty columns, auto-fill keeps them', () => {
const two = { min: 200, max: '1fr', gap: 16, items: 2 };
near(columnWidths(1200, { ...two, fit: false }), [227.2, 227.2, 227.2, 227.2, 227.2], 'auto-fill, 2 cards');
near(columnWidths(1200, { ...two, fit: true }), [592, 592, 0, 0, 0], 'auto-fit, 2 cards');
near(columnWidths(1200, { ...two, items: 12, fit: true }), [227.2, 227.2, 227.2, 227.2, 227.2], 'auto-fit, 12 cards');
});
test('a length as the maximum stops the columns growing', () => {
near(columnWidths(1000, { min: 200, max: 300, gap: 0, items: 6, fit: false }), [300, 300, 300], 'minmax(200px, 300px)');
near(columnWidths(1000, { min: 200, max: 300, gap: 0, items: 1, fit: true }), [300, 0, 0], 'one card, auto-fit');
});
test('min(16rem, 100%) keeps one column inside a narrow grid', () => {
assert.equal(safeMinimum(256, 248), 248);
assert.equal(safeMinimum(256, 900), 256);
near(columnWidths(248, { ...card, min: safeMinimum(256, 248) }), [248], '248px with min()');
}); A hint
n columns and their gaps need n * size + (n - 1) * gap px, so the largest n that fits is Math.floor((width + gap) / (size + gap)), raised to 1 if it is 0. For auto-fit, only Math.min(items, n) columns share the space, and the rest are 0.
Results of the sample tests
| Test | Result | Details |
|---|
What your code printed
The sample tests run on this device, in your browser (QuickJS): nothing is sent to mysmartcopilot.com. The first run downloads JavaScript (about 0.6 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.
Interview questions
Fresher · What is the difference between auto-fill and auto-fit in repeat()?
Both repeat a track as many times as fits in the grid with its gaps, and count the same way: with repeat(auto-fill, minmax(200px, 1fr)) in a 1200 px grid with 16 px gaps, both make five 200 px slots. The difference appears when there
are fewer items than tracks. auto-fill keeps the empty tracks, so two cards stay 227.2 px wide and the rest of the
row stays empty. auto-fit collapses every empty track, and its gaps, to zero, so the two cards share the whole row
and become 592 px wide. With five or more cards the results are identical. Use auto-fill when cards should keep their
size whatever their number, as in search results, and auto-fit when a small, fixed set should fill the row.
Key takeaways
repeat(auto-fill, minmax(16rem, 1fr))makes as many 16rem columns as fit, at least one, then shares the rest.- The count uses the maximum when it is a length:
minmax(200px, 300px)counts 300 px columns. auto-fitcollapses empty tracks to 0, so a few items stretch;auto-fillkeeps them, so items keep their size.- A bare
1frisminmax(auto, 1fr): long unbreakable content widens its column and can push the grid off screen. - Fix blowouts with
minmax(0, 1fr)(ormin-inline-size: 0) plusoverflow-wrap: anywhere;break-worddoes not change the min-content width. minmax(min(20rem, 100%), 1fr)keeps a single column inside screens narrower than the minimum.- Size label and button columns with
fit-content(<limit>);max-contentnever wraps and can squeeze its neighbours.
Check yourself
6 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.
References
- CSS Grid Layout Module Level 2: repeat-to-fill, auto-fill and auto-fit repetitions (W3C CSS Working Group)
- CSS Grid Layout Module Level 2: the minmax() notation (W3C CSS Working Group)
- CSS Grid Layout Module Level 2: track sizes (W3C CSS Working Group)
- CSS Grid Layout Module Level 2: automatic minimum size of grid items (W3C CSS Working Group)
- CSS Grid Layout Module Level 2: the fit-content() track size (W3C CSS Working Group)
- CSS Text Module Level 3: the overflow-wrap property (W3C CSS Working Group)
- repeat() (MDN Web Docs) (Mozilla)
- minmax() (MDN Web Docs) (Mozilla)
- CSS Box Sizing Module Level 3: percentage sizes of replaced elements (W3C CSS Working Group)
- Understanding Success Criterion 1.4.10: Reflow (W3C Web Accessibility Initiative)
- GST frequently asked questions (Central Board of Indirect Taxes and Customs, Government of India)
- CSS grid layout (MDN Web Docs) (Mozilla)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress