Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

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.

  • Intermediate
  • 25 minutes
  • Examples run with Node.js 24.21.0 and quickjs 0.32.0
  • By MySmartCoPilot
Free: 25 minutes

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:

Columns of repeat(auto-fill, minmax(16rem, 1fr)) at six widths JavaScript · columns.mjs
// 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

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:

Which size decides the number of columns? HTML · count-rule.html
<!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:

auto-fill and auto-fit with 2, 5 and 12 cards HTML · auto-fill-fit.html
<!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>
With two cards in 1200 px, auto-fill keeps five 227 px tracks and three stay empty; auto-fit collapses them and the cards are 592 px.Two cards in a 1200 px grid, 16 px gapsrepeat(…, minmax(200px, 1fr))auto-fill: all 5 tracks are keptCard 1227.2 pxCard 2227.2 pxemptytrackemptytrackemptytrackauto-fit: the 3 empty tracks collapse to 0 pxCard 1592 pxCard 2592 pxWith 5 or more cards both keywords give the same5 columns; they differ only when tracks are empty.

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.

  1. 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.
  2. 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.
  3. 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-fill keeps 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-fit lets 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.
CSS Grid Generator Start from the auto-fit gallery preset and change the minimum or the gap to see how many columns each breakpoint gets.

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:

A long link in a three-column grid HTML · blowout.html
<!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: anywhere lets the browser break an unbreakable string at any character, and, unlike break-word, it counts those breaks when it works out the min-content width. That is why break-word changed nothing here and anywhere fixed 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: 0 on the items does the same job as minmax(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:

A card grid on narrow screens HTML · narrow.html
<!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-content is the content’s width on one line, however long.
  • min-content is 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.
  • auto behaves like max-content as 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:

Label columns sized by their content HTML · content-tracks.html
<!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.

PX to REM Converter Convert the pixel widths of a design into rem before you write minimums such as 16rem or 20rem.

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.

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-fit collapses empty tracks to 0, so a few items stretch; auto-fill keeps them, so items keep their size.
  • A bare 1fr is minmax(auto, 1fr): long unbreakable content widens its column and can push the grid off screen.
  • Fix blowouts with minmax(0, 1fr) (or min-inline-size: 0) plus overflow-wrap: anywhere; break-word does 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-content never 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.

  1. Question 1 of 6 How many columns does repeat(auto-fill, minmax(250px, 1fr)) make in a grid 1100px wide with gap: 20px?

    Type a number.

    Show the answer to question 1

    Answer: 4

    The browser counts with the minimum because the maximum is 1fr: n columns and n − 1 gaps fit while n × 250 + (n − 1) × 20 ≤ 1100, which holds for n = 4 (1060 px) but not for 5 (1330 px). The four columns then share the leftover space as 1fr: (1100 − 60) ÷ 4 = 260 px each.

  2. Question 2 of 6 A search page shows its results with repeat(auto-fit, minmax(16rem, 1fr)). With one result, the card stretches across the whole page. Why?

    Choose one answer.

    Show the answer to question 2

    Answer: auto-fit collapses the empty tracks to 0, so the one card's 1fr track takes all the space

    auto-fill would keep the empty tracks and the card would stay one column wide. auto-fit collapses every empty track and its gaps, so the remaining tracks share the space; in the lesson's run two cards in 1200 px became 592 px each instead of 227.2 px. Use auto-fill, or give the maximum a length, when cards must not stretch.

  3. Question 3 of 6 How many columns does repeat(auto-fill, minmax(200px, 300px)) make in a grid 1000px wide with no gap?

    Type a number.

    Show the answer to question 3

    Answer: 3

    When the maximum is a length, the browser counts with the maximum, not the minimum: three 300 px columns fit in 1000 px, a fourth does not. All three engines drew 3 columns and left 100 px empty, where minmax(200px, 1fr) gave 5 columns of 200 px.

  4. Question 4 of 6 A repeat(3, 1fr) grid on a phone has one column far wider than the others because a cell holds a long link. Which changes make the three columns equal? Choose every one that works.

    Choose every answer that is right.

    Show the answer to question 4

    Answer:

    • overflow-wrap: anywhere on the cells
    • repeat(3, minmax(0, 1fr))

    A bare 1fr has an automatic minimum, the content's min-content width, so an unbreakable link widens its column. minmax(0, 1fr) removes that minimum, and overflow-wrap: anywhere lowers the min-content width itself. In the lesson's run, break-word left the columns at 38, 347 and 35 px, because it allows breaks without changing the min-content width. Percentages plus gaps would overflow the grid.

  5. Question 5 of 6 Why write minmax(min(20rem, 100%), 1fr) instead of minmax(20rem, 1fr) for a card grid?

    Choose one answer.

    Show the answer to question 5

    Answer: So that on a screen narrower than 20rem the single column shrinks to the grid instead of overflowing it

    A minimum of 20rem (320 px) is wider than the 248 px a 280 px screen leaves after 16 px of padding on each side, and a grid column never goes below its minimum, so the page scrolled sideways by 56 px in all three engines. min(20rem, 100%) is 248 px there and 20rem everywhere else.

  6. Question 6 of 6 A form summary uses grid-template-columns: max-content 1fr. One label is a 52-character phrase. What happens to the value column in a 600px list?

    Choose one answer.

    Show the answer to question 6

    Answer: It is squeezed, because the label column takes the whole label on one line, about 393 px

    max-content is the width of the content with no wrapping, so the long GSTIN label made the first column about 393 px wide and left the values about 191 px. fit-content(12rem) caps the column at 192 px and lets the long label wrap over three lines, while short labels keep their natural width.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.