CSS Module 10 – Flexbox
How flex items grow, shrink and size
Predict flex item sizes from flex-grow, flex-shrink and flex-basis, decode flex: 1, auto and none, and fix items that overflow because of their minimum size.
What you will learn
- Predict item sizes from flex-grow, flex-shrink and flex-basis
- Explain what flex: 1, flex: auto and flex: none mean
- Fix overflowing flex items caused by the automatic minimum size
Before you start
What you’ll learn · Before you start
What you will learn
- Predict item sizes from flex-grow, flex-shrink and flex-basis
- Explain what flex: 1, flex: auto and flex: none mean
- Fix overflowing flex items caused by the automatic minimum size
Before you start
On this page
A flex item’s size along the main axis comes out of three steps. It starts from its flex-basis, which by default
is its width, or the width of its content. If the items’ bases add up to less than the line, the free space is
shared out in proportion to their flex-grow values; if they add up to more, the missing space is taken back in
proportion to flex-shrink multiplied by each basis. Finally every item is kept between its minimum and maximum
size, and a flex item’s minimum is, by default, the width of its longest unbreakable content. That last rule is
behind many overflowing flex layouts. The flex shorthand sets all three numbers at once: flex: 1 is 1 1 0, flex: auto is 1 1 auto and flex: none is 0 0 auto. Each table sets the widths that Chromium 153, Firefox 155 and WebKit 26.6 drew
beside the arithmetic that predicts them.
Where each item starts: flex-basis
The flex basis is the size an item asks for before any sharing. Its default, auto, means “use my width” in a row
(or my height in a column), and when that is auto too, the size of the content laid out on one line. A length or
a percentage replaces the width for this purpose, and a percentage is a share of the container. The limits
min-width and max-width still apply at the end:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>flex-basis, width and the limits</title>
<style>
body { margin: 0; font: 16px/24px Arial, sans-serif; }
.row { display: flex; inline-size: 600px; margin-block-end: 8px; background: #eee; }
.row > div { box-sizing: border-box; background: #e6edfc; }
#basis-wins > div { inline-size: 300px; flex: 0 1 100px; } /* a basis other than auto beats the width */
#auto-uses-width > div { inline-size: 300px; flex: 0 1 auto; } /* auto: the basis is the width */
#max-clamps > div { flex: 1 1 200px; max-inline-size: 120px; } /* the limits still apply */
#min-clamps { inline-size: 200px; }
#min-clamps > div { flex: 1 1 0; min-inline-size: 150px; }
#half > div { flex: 0.5 1 100px; } /* factors that add up to less than 1 */
</style>
</head>
<body>
<div class="row" id="basis-wins"><div>Item</div></div>
<div class="row" id="auto-uses-width"><div>Item</div></div>
<div class="row" id="max-clamps"><div>Item</div></div>
<div class="row" id="min-clamps"><div>One</div><div>Two</div></div>
<div class="row" id="half"><div>Item</div></div>
<script>
for (const row of document.querySelectorAll('.row')) {
const widths = [...row.children].map((d) => d.getBoundingClientRect().width);
console.log(`${row.id}: ${widths.join(', ')} in ${row.getBoundingClientRect().width}px (overflow ${row.scrollWidth - row.clientWidth}px)`);
}
</script>
</body>
</html> | Item, in a 600 px row | Width |
|---|---|
width: 300px; flex: 0 1 100px |
100 px |
width: 300px; flex: 0 1 auto |
300 px |
flex: 1 1 200px; max-width: 120px |
120 px |
two items with flex: 1 1 0; min-width: 150px, in a 200 px row |
150 px each |
flex: 0.5 1 100px |
350 px |
All three engines agreed on every row. A basis other than auto beat the width, auto took the width, and the
maximum still capped the item. The two items with a 150 px minimum kept it and stuck out of their 200 px row by
100 px: when space runs out, the minimum wins. The last row previews the next section: the item asked for 100 px,
500 px were free, and a lone grow factor of 0.5 took just that fraction of them.
Sharing free space: flex-grow
When the bases add up to less than the line, the difference is the free space, and each item gets a share of it
in proportion to its flex-grow. An item with flex-grow: 0, the default, gets nothing. Here are three items, A, B and
C, in containers of four widths:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Three items at four widths</title>
<style>
body { margin: 0; font: 16px/24px Arial, sans-serif; }
.row { display: flex; margin-block-end: 8px; background: #eee; }
.row > div { box-sizing: border-box; background: #e6edfc; }
.a { flex: 1 1 100px; } /* grow 1, shrink 1, basis 100px */
.b { flex: 2 1 200px; } /* grow 2, shrink 1, basis 200px */
.c { flex: 0 3 150px; } /* never grows, shrinks three times as readily */
.icon { display: inline-block; inline-size: 40px; } /* C can't be narrower than this */
.w600 { inline-size: 600px; }
.w480 { inline-size: 480px; }
.w360 { inline-size: 360px; }
.w240 { inline-size: 240px; }
</style>
</head>
<body>
<div class="row w600"><div class="a">A</div><div class="b">B</div><div class="c"><span class="icon">C</span></div></div>
<div class="row w480"><div class="a">A</div><div class="b">B</div><div class="c"><span class="icon">C</span></div></div>
<div class="row w360"><div class="a">A</div><div class="b">B</div><div class="c"><span class="icon">C</span></div></div>
<div class="row w240"><div class="a">A</div><div class="b">B</div><div class="c"><span class="icon">C</span></div></div>
<script>
for (const row of document.querySelectorAll('.row')) {
const w = [...row.children].map((d) => `${d.textContent} ${+d.getBoundingClientRect().width.toFixed(2)}`);
console.log(`${row.getBoundingClientRect().width}px: ${w.join(', ')}`);
}
</script>
</body>
</html> A asks for 100 px, B for 200 px and C for 150 px, 450 px in total. Their grow factors are 1, 2 and 0:
| Row | A | B | C |
|---|---|---|---|
| 600 px | 150 px | 300 px | 150 px |
| 480 px | 110 px | 220 px | 150 px |
At 600 px, 150 px were free: A got one share and B two shares, 50 and 100 px. At 480 px, 30 px were free, so A grew by 10 px and B by 20 px. C never grows. The free space is measured between the items’ outer edges, so padding, borders, margins and gaps come off the line before anything is shared.
Taking space back: flex-shrink
When the bases add up to more than the line, the free space is negative, and the items shrink. This time the share
each item gives up is in proportion to its flex-shrink multiplied by its basis (without padding and borders), so
a wide item gives up more than a narrow one with the same factor. The default, flex-shrink: 1, lets every item
shrink; 0 stops it. At 360 px the three bases are 90 px too wide, and the shrink factors are 1, 1 and 3:
| Row | A | B | C |
|---|---|---|---|
| 360 px | 88 px | 176 px | 96 px |
The weights are 100, 200 and 450, which add up to 750. A gives up 90 × 100 ÷ 750 = 12 px, B 24 px and C 54 px. Shrinking by flex-shrink alone, without the basis, would have taken 18, 18 and 54 px, and a small icon would shrink just as fast as a wide column of text; weighting by the basis keeps the shrinking proportional to the size.
How three flex items grow and shrink
Text description of the diagram
The diagram draws the lesson's three flex items to scale. A has flex: 1 1 100px, B flex: 2 1 200px and C flex: 0 3 150px, so their bases add up to 450px.
- In a 480px container, 30px are left over. The free space goes by flex-grow, 1 : 2 : 0, so A grows by 10px to 110px, B by 20px to 220px, and C, whose flex-grow is 0, stays at 150px.
- In a 360px container the bases stick out by 90px. The missing space is taken in proportion to flex-shrink times flex-basis: 1 × 100, 1 × 200 and 3 × 150, a ratio of 100 : 200 : 450. A gives up 12px and ends at 88px, B gives up 24px and ends at 176px, and C gives up 54px and ends at 96px, because it shrinks three times as readily and from a larger basis.
Minimums and maximums: when an item is frozen
The 240 px container needs 210 px back. The same arithmetic gives A 72 px, B 144 px and C 24 px, but C holds a 40 px icon and cannot be narrower than that. The browser freezes C at its 40 px minimum and shares the shortfall again among the items that are still flexible: 240 − 40 = 200 px for A and B, which asked for 300 px, so they give up 100 px between them in the ratio 100 : 200. All three engines drew the result:
| Row | A | B | C |
|---|---|---|---|
| 240 px | 66.67 px | 133.33 px | 40 px |
This loop is how the specification resolves flexible lengths: share the space, clamp every item to its minimum and maximum, freeze the items that hit a limit, and share again among the rest until nothing more changes. Here is the loop as a program, run on the same three items:
// How one flex line shares out space, following "Resolving Flexible Lengths" in CSS Flexible Box Layout Level 1, for
// the three items of grow-shrink.html. Chromium 153, Firefox 155 and WebKit 26.6 drew exactly these widths.
// A and B hold one letter each, far narrower than any size they reach here, so only C's 40px minimum matters.
const items = [
{ name: 'A', grow: 1, shrink: 1, basis: 100, min: 0, max: Infinity },
{ name: 'B', grow: 2, shrink: 1, basis: 200, min: 0, max: Infinity },
{ name: 'C', grow: 0, shrink: 3, basis: 150, min: 40, max: Infinity },
];
const round = (x) => Math.round(x * 100) / 100;
const sum = (list, f) => list.reduce((total, x) => total + f(x), 0);
const names = (list) => list.map((it) => it.name).join(', ').replace(/, ([^,]*)$/, ' and $1');
// The minimum wins when the two limits disagree.
const clamp = (it, size) => Math.max(it.min, Math.min(it.max, size));
function resolve(container) {
const growing = sum(items, (it) => it.basis) < container;
const size = new Map();
// An item whose factor is 0 keeps its basis: it is frozen from the start.
for (const it of items) if ((growing ? it.grow : it.shrink) === 0) size.set(it, it.basis);
for (;;) {
const open = items.filter((it) => !size.has(it));
const free = container - sum(items, (it) => (size.has(it) ? size.get(it) : it.basis));
// Growing shares free space by flex-grow; shrinking takes it away by flex-shrink × basis.
const weight = (it) => (growing ? it.grow : it.shrink * it.basis);
const total = sum(open, weight);
const target = new Map(open.map((it) => [it, it.basis + (free * weight(it)) / total]));
console.log(` ${free >= 0 ? `${round(free)}px to share` : `${round(-free)}px too little`} between ${names(open)}, ` +
`in the ratio ${open.map(weight).join(' : ')} → ${open.map((it) => `${it.name} ${round(target.get(it))}`).join(', ')}`);
// Clamp every target to its limits. If nothing moved, the sizes are final. Otherwise freeze the items that broke a
// minimum (when clamping added space overall) or a maximum (when it took space away), and share again.
const violation = sum(open, (it) => clamp(it, target.get(it)) - target.get(it));
if (violation === 0) {
for (const it of open) size.set(it, target.get(it));
return items.map((it) => `${it.name} ${round(size.get(it))}`).join(', ');
}
for (const it of open) {
const limited = clamp(it, target.get(it));
if (violation > 0 ? limited > target.get(it) : limited < target.get(it)) {
console.log(` ${it.name} can't go ${violation > 0 ? `below its ${it.min}px minimum` : `above its ${it.max}px maximum`}: it is frozen at ${limited}px`);
size.set(it, limited);
}
}
}
}
console.log(`Every item starts from its flex-basis: ${items.map((it) => it.basis).join(' + ')} = ${sum(items, (it) => it.basis)}px`);
for (const container of [600, 480, 360, 240]) {
console.log(`${container}px:`);
console.log(` final widths: ${resolve(container)}`);
} Output
Every item starts from its flex-basis: 100 + 200 + 150 = 450px 600px: 150px to share between A and B, in the ratio 1 : 2 → A 150, B 300 final widths: A 150, B 300, C 150 480px: 30px to share between A and B, in the ratio 1 : 2 → A 110, B 220 final widths: A 110, B 220, C 150 360px: 90px too little between A, B and C, in the ratio 100 : 200 : 450 → A 88, B 176, C 96 final widths: A 88, B 176, C 96 240px: 210px too little between A, B and C, in the ratio 100 : 200 : 450 → A 72, B 144, C 24 C can't go below its 40px minimum: it is frozen at 40px 100px too little between A and B, in the ratio 100 : 200 → A 66.67, B 133.33 final widths: A 66.67, B 133.33, C 40
Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node share_space.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
A max-width works the same way in the other direction: an item that would grow past its maximum is frozen there,
and the others take its share.
The flex shorthand: flex: 1, auto, none
The flex shorthand sets grow, shrink and basis in one declaration, and its keywords cover the common cases:
flex: |
Grow, shrink, basis | The item |
|---|---|---|
initial (default) |
0 1 auto |
keeps its size, may shrink |
auto |
1 1 auto |
its size plus a share |
none |
0 0 auto |
keeps its size |
1, 2 |
1 1 0, 2 1 0 |
a share by the number |
10rem |
1 1 10rem |
starts at 10rem |
initial never grows but may shrink when the row is too narrow, auto starts from the item’s own size and then
takes a share of the free space, and none neither grows nor shrinks. A plain number starts the item from nothing,
so the line is shared in proportion to the numbers, and a length starts it from that length.
The parts you leave out of the shorthand are filled in differently from the properties’ initial values: a missing
grow becomes 1 and a missing basis becomes 0. That is why flex: 1 and flex-grow: 1 behave differently: the first
sets the basis to 0, the second leaves it at auto. The specification writes the expansion of flex: 1 as 1 1 0,
and all three engines read the basis back as 0%.
A navigation bar with three links of different lengths shows the difference: Home, Products and services, and Contact. Each link has 8 px of padding on both sides, in a 600 px row, and the table gives their widths in px:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>flex: 1, auto, none and initial</title>
<style>
body { margin: 0; font: 16px/24px Arial, sans-serif; }
.row { display: flex; inline-size: 600px; margin-block-end: 8px; background: #eee; }
.row > a { padding-inline: 8px; background: #e6edfc; }
#one > a { flex: 1; }
#auto > a { flex: auto; }
#none > a { flex: none; }
#initial > a { flex: initial; }
#two > a:first-child { flex: 2; }
#two > a:not(:first-child) { flex: 1; }
</style>
</head>
<body>
<nav class="row" id="one"><a href="#a">Home</a><a href="#b">Products and services</a><a href="#c">Contact</a></nav>
<nav class="row" id="auto"><a href="#a">Home</a><a href="#b">Products and services</a><a href="#c">Contact</a></nav>
<nav class="row" id="none"><a href="#a">Home</a><a href="#b">Products and services</a><a href="#c">Contact</a></nav>
<nav class="row" id="initial"><a href="#a">Home</a><a href="#b">Products and services</a><a href="#c">Contact</a></nav>
<nav class="row" id="two"><a href="#a">Home</a><a href="#b">Products and services</a><a href="#c">Contact</a></nav>
<script>
for (const row of document.querySelectorAll('.row')) {
const first = getComputedStyle(row.firstElementChild);
const widths = [...row.children].map((a) => +a.getBoundingClientRect().width.toFixed(2));
console.log(`${row.id}: computed ${first.flexGrow} ${first.flexShrink} ${first.flexBasis}; widths ${widths.join(', ')}`);
}
</script>
</body>
</html> flex |
Home, Products, Contact |
|---|---|
1 |
200, 200, 200 |
auto |
157.61, 272.33, 170.06 |
none |
58.69, 173.41, 71.14 |
2, 1, 1 |
292, 154, 154 |
These are Chromium’s widths; Firefox and WebKit were within 0.03 px, and flex: initial gave the same widths as
none. In the last row Home has flex: 2 and the other two flex: 1. With flex: 1 the links ignored their text and
came out equal. With flex: auto each link kept the width of its text and added an equal 98.92 px share of the
296.76 px left over, so long labels stayed longer. none and initial both kept the text widths here, because there
was room; the difference is that initial may still shrink when the row gets too narrow. The last row is the
surprise: flex: 2 did not make Home twice as wide. The 48 px of padding sat outside the shared space, so the shares
were 276, 138 and 138 px of content, and the padding brought them to 292, 154 and 154 px.
When wrapped lines fill unevenly, items with a grow factor on a short last line become much wider than the rest.
flex-wrap: balance spreads the items over the lines more evenly, but it is part of the Level 2 draft and only
Chrome and Edge 150 support it, so for now a grid is the reliable way to keep equal columns. The patterns lesson of
this module measures that case.
The automatic minimum size, and why flex items overflow
The default min-width of a flex item is auto, and for a flex item auto means a content-based minimum: the
item’s min-content width, the width of its longest piece of content that cannot break, or the item’s own width when
that is set and smaller. A scroll container, an item with overflow set to hidden, auto or scroll, has a
minimum of 0 instead. This is why a long word, a URL, a code sample or a checksum can push a flex layout off the
screen: the item refuses to get narrower than it.
A sidebar layout on a 360 px phone: a 96 px sidebar, a 16 px gap and a flex: 1 main column with 248 px of room,
which holds a 48-character checksum. In the table, break-word and anywhere are values of overflow-wrap on the
text, hidden and clip values of overflow on the column, “min 0” is min-inline-size: 0 on the column, and the
last two rows hold a pre with overflow-x: auto instead of the checksum. Widths are in px:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>The automatic minimum size of flex items</title>
<style>
body { margin: 0; font: 16px/24px Arial, sans-serif; }
.layout { display: flex; gap: 16px; margin-block-end: 8px; }
.sidebar { flex: 0 0 96px; background: #eee; }
.main { flex: 1; background: #e6edfc; }
.main p, .main pre { margin: 0; }
#break-word p { overflow-wrap: break-word; }
#anywhere p { overflow-wrap: anywhere; }
#min-zero .main { min-inline-size: 0; }
#min-zero-break .main { min-inline-size: 0; }
#min-zero-break p { overflow-wrap: break-word; }
#hidden .main { overflow: hidden; }
#clip .main { overflow: clip; }
#pre-scroll pre, #pre-scroll-min pre { overflow-x: auto; }
#pre-scroll-min .main { min-inline-size: 0; }
</style>
</head>
<body>
<div class="layout" id="no-fix"><nav class="sidebar">Menu</nav><main class="main"><p>8f14e45fceea167a5a36dedd4bea2543c4f8a4b9e1d9b6e4</p></main></div>
<div class="layout" id="break-word"><nav class="sidebar">Menu</nav><main class="main"><p>8f14e45fceea167a5a36dedd4bea2543c4f8a4b9e1d9b6e4</p></main></div>
<div class="layout" id="anywhere"><nav class="sidebar">Menu</nav><main class="main"><p>8f14e45fceea167a5a36dedd4bea2543c4f8a4b9e1d9b6e4</p></main></div>
<div class="layout" id="min-zero"><nav class="sidebar">Menu</nav><main class="main"><p>8f14e45fceea167a5a36dedd4bea2543c4f8a4b9e1d9b6e4</p></main></div>
<div class="layout" id="min-zero-break"><nav class="sidebar">Menu</nav><main class="main"><p>8f14e45fceea167a5a36dedd4bea2543c4f8a4b9e1d9b6e4</p></main></div>
<div class="layout" id="hidden"><nav class="sidebar">Menu</nav><main class="main"><p>8f14e45fceea167a5a36dedd4bea2543c4f8a4b9e1d9b6e4</p></main></div>
<div class="layout" id="clip"><nav class="sidebar">Menu</nav><main class="main"><p>8f14e45fceea167a5a36dedd4bea2543c4f8a4b9e1d9b6e4</p></main></div>
<div class="layout" id="pre-scroll"><nav class="sidebar">Menu</nav><main class="main"><pre>npm install --save-dev stylelint stylelint-config-standard</pre></main></div>
<div class="layout" id="pre-scroll-min"><nav class="sidebar">Menu</nav><main class="main"><pre>npm install --save-dev stylelint stylelint-config-standard</pre></main></div>
<div class="layout" id="url"><nav class="sidebar">Menu</nav><main class="main"><p>https://example.com/downloads/FlexSizingHandbookSecondEdition.pdf</p></main></div>
<script>
// How wide main became, how wide its content is, and how far the layout sticks out past the screen's edge.
for (const layout of document.querySelectorAll('.layout')) {
const main = layout.querySelector('.main');
const content = main.firstElementChild;
console.log(`${layout.id}: main ${+main.getBoundingClientRect().width.toFixed(2)}px, content ${content.scrollWidth}px, sticks out ${layout.scrollWidth - layout.clientWidth}px`);
}
</script>
</body>
</html> | Fix | Width | Sticks out |
|---|---|---|
| none | 412 | 164 |
break-word |
412 | 164 |
anywhere |
248 | 0 |
| min 0 | 248 | 164 |
min 0, break-word |
248 | 0 |
hidden |
248 | 0 |
clip |
412 | 164 |
pre |
557 | 309 |
pre, min 0 |
248 | 0 |
The numbers are Chromium’s; Firefox and WebKit agreed, except that their pre was 559 px wide and stuck out by
311 px. With min-inline-size: 0 alone the column fitted, but the checksum spilled out of it. Adding break-word or
using anywhere made the checksum wrap, hidden cut off its end, and in the last row the pre scrolled inside the
column.
Three results surprise people. overflow-wrap: break-word changed nothing, because it breaks a word only once the
box is already sized, and it does not lower the min-content width, so the flex item kept its 412 px minimum.
overflow-wrap: anywhere fixed it alone, because its break points do count for the min-content width. And
overflow: clip did not help where overflow: hidden did: clip cuts off the overflow without making a scroll
container, so the item keeps its content-based minimum. The reliable fix is min-inline-size: 0 on the flex item
together with a decision about the content: let it wrap, let it scroll in its own box, or cut it off.
Engines also differ in where text may break. A long URL without hyphens, https://example.com/downloads/
followed by one long file name, made the same column 505 px wide in Chromium and WebKit but only 277 px in Firefox,
because Firefox broke the address after a slash and the others did not. A layout that looks fine in one browser can
therefore overflow in another; min-inline-size: 0 with overflow-wrap: anywhere makes them all behave alike.
A media object that truncates its title
A media object, a fixed picture beside text, is the classic place where the minimum size bites. Here the title must stay on one line and end with “…” when it is too long:
/* A media object: a fixed picture beside text that takes the rest of the line. The title stays on one line and
ends with "…" when it is too long. */
.media {
display: flex;
gap: 0.75rem;
align-items: flex-start;
}
.media .avatar {
flex: none; /* 0 0 auto: the picture never grows or shrinks */
inline-size: 3rem;
block-size: 3rem;
}
.media .body {
flex: 1;
min-inline-size: 0; /* without this, the one-line title keeps the item as wide as the whole title */
}
.media .title {
margin: 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
} <!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>A media object that truncates its title</title>
<link rel="stylesheet" href="media-object.css">
<style>
body { margin: 0; font: 16px/24px Arial, sans-serif; }
.media { margin-block-end: 8px; }
.avatar { background: #e6edfc; }
#without-min .body { min-inline-size: auto; } /* the default, for comparison */
</style>
</head>
<body>
<article class="media" id="with-min">
<div class="avatar"></div>
<div class="body"><p class="title">Order 4471 shipped: two notebooks, a fountain pen and a desk lamp</p><p>Arrives on Thursday</p></div>
</article>
<article class="media" id="without-min">
<div class="avatar"></div>
<div class="body"><p class="title">Order 4471 shipped: two notebooks, a fountain pen and a desk lamp</p><p>Arrives on Thursday</p></div>
</article>
<script>
for (const media of document.querySelectorAll('.media')) {
const body = media.querySelector('.body').getBoundingClientRect();
const title = media.querySelector('.title');
console.log(`${media.id} at ${innerWidth}px: body ${+body.width.toFixed(2)}px, title needs ${title.scrollWidth}px and shows ${title.clientWidth}px (${title.scrollWidth > title.clientWidth ? 'cut with an ellipsis' : 'fits'}), sticks out ${media.scrollWidth - media.clientWidth}px`);
}
</script>
</body>
</html> On a 360 px phone, with min-inline-size: 0, the text column was 300 px wide and the 486 px title was cut to 300 px
with an ellipsis, in all three engines. Without it, the column grew to the title’s full 486 px and the card stuck out
of the screen by 186 px, because white-space: nowrap turned the whole title into one unbreakable line, which
became the item’s minimum. The overflow: hidden on the title does not help on its own: the title is not the flex item, its parent
is. At 768 px both versions fitted, which is why this bug often hides until someone opens the page on a phone.
Practise: an item’s automatic minimum width
Exercise · Medium · JavaScript
Work out a flex item's automatic minimum width
A flex item in a row never gets narrower than its minimum width. Write two functions in minimum.mjs and export them. All lengths are in px.
The first, autoMinWidth(item), returns that minimum for an item described by an object:
minContent: the item's min-content width (always given);minWidth:'auto'(the default when it is missing) or a number set withmin-widthormin-inline-size;width: a number whenwidthorinline-sizeis set to a length, missing when it isauto;maxWidth: a number whenmax-widthis set, missing otherwise;overflow:'visible'(the default),'hidden','clip','auto'or'scroll'.
Follow the flexbox specification. A minWidth that is a number is the minimum. With 'auto', an item that is a scroll container (hidden, auto or scroll) has a minimum of 0; any other item has a content-based minimum: the smaller of width (when it is given) and minContent, and never more than maxWidth.
The second, itemWidth(available, item), returns the width a flex: 1 item gets when available px are left for it on its line: the available space, but no wider than maxWidth and never narrower than the item's minimum. When the two limits disagree, the minimum wins.
The sample tests import both functions from minimum.mjs and run in your browser. The 412 px item is the lesson's checksum in a 248 px column.
Starter code · minimum.mjs
/** The minimum width of a flex item in a row (min-width: auto resolves to the automatic minimum). */
export function autoMinWidth(item) {
// Replace this line with your code.
return 0;
}
/** The width of a flex: 1 item when `available` px are left for it on its line. */
export function itemWidth(available, item) {
// Replace this line with your code.
return available;
} The sample tests · minimum.test.mjs
import { test, assert } from 'mysmartcopilot:test';
import { autoMinWidth, itemWidth } from './minimum.mjs';
const checksum = { minContent: 412 };
test('by default the item is as wide as its longest unbreakable content', () => {
assert.equal(autoMinWidth(checksum), 412);
assert.equal(itemWidth(248, checksum), 412);
});
test('min-width: 0 lets the item shrink to the space', () => {
assert.equal(autoMinWidth({ minContent: 412, minWidth: 0 }), 0);
assert.equal(itemWidth(248, { minContent: 412, minWidth: 0 }), 248);
assert.equal(itemWidth(248, { minContent: 412, minWidth: 300 }), 300);
});
test('a scroll container has no content-based minimum, but clip is not one', () => {
assert.equal(autoMinWidth({ minContent: 412, overflow: 'hidden' }), 0);
assert.equal(autoMinWidth({ minContent: 412, overflow: 'auto' }), 0);
assert.equal(autoMinWidth({ minContent: 412, overflow: 'scroll' }), 0);
assert.equal(autoMinWidth({ minContent: 412, overflow: 'clip' }), 412);
assert.equal(autoMinWidth({ minContent: 412, overflow: 'visible' }), 412);
});
test('a width set as a length can lower the minimum, never raise it', () => {
assert.equal(autoMinWidth({ minContent: 412, width: 200 }), 200);
assert.equal(autoMinWidth({ minContent: 120, width: 300 }), 120);
});
test('max-width caps the minimum and the width', () => {
assert.equal(autoMinWidth({ minContent: 412, maxWidth: 300 }), 300);
assert.equal(itemWidth(500, { minContent: 100, maxWidth: 300 }), 300);
assert.equal(itemWidth(500, { minContent: 100 }), 500);
});
test('when min-width and max-width disagree, the minimum wins', () => {
assert.equal(itemWidth(200, { minContent: 50, minWidth: 350, maxWidth: 300 }), 350);
}); A hint
Check the cases in order: a number in minWidth first, then a scroll container, then the content-based minimum Math.min(width ?? Infinity, minContent), capped by maxWidth ?? Infinity. itemWidth caps available by maxWidth first and only then raises it to the minimum, so the minimum wins a conflict.
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.
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.
Interview questions
Fresher · What do flex: 1, flex: auto and flex: none mean, and when would you use each?
Each sets flex-grow, flex-shrink and flex-basis together. flex: 1 is 1 1 0: the item starts from a basis of 0, so
items with flex: 1 share the line equally whatever their content, which suits equal columns or tabs. flex: auto
is 1 1 auto: each item starts from its own size and then takes an equal share of what is left, so longer content
stays longer, as in a navigation bar where labels differ. flex: none is 0 0 auto: the item keeps its own size and
never grows or shrinks, which suits icons, avatars and buttons beside a flexible column. In a 600 px row of three
links with 8 px padding, flex: 1 gave 200 px each, while flex: auto gave 158, 272 and 170 px. The default, flex: initial, is 0 1 auto: no growing, but it may shrink.
Key takeaways
- An item starts from its flex-basis:
automeans its width, and when that isauto, its content’s width. - Free space is shared by flex-grow; missing space is taken back by flex-shrink multiplied by the basis, so wider items give up more.
- Items that hit their minimum or maximum are frozen, and the rest share the space again: at 240 px, 66.67, 133.33 and 40 px.
flex: 1is1 1 0(equal shares),flex: autois1 1 auto(content plus a share) andflex: noneis0 0 auto(fixed).- A flex item’s default minimum is its min-content width. Give it
min-inline-size: 0and decide whether its content wraps, scrolls or is cut off;overflow-wrap: break-wordalone does not shrink it.
References
- CSS Flexible Box Layout Module Level 1: the flex shorthand (W3C CSS Working Group)
- CSS Flexible Box Layout Module Level 1: common values of flex (W3C CSS Working Group)
- CSS Flexible Box Layout Module Level 1: the flex-basis property (W3C CSS Working Group)
- CSS Flexible Box Layout Module Level 1: resolving flexible lengths (W3C CSS Working Group)
- CSS Flexible Box Layout Module Level 1: automatic minimum size of flex items (W3C CSS Working Group)
- CSS Box Sizing Module Level 3: intrinsic sizes (W3C CSS Working Group)
- CSS Text Module Level 3: overflow-wrap (W3C CSS Working Group)
- CSS Overflow Module Level 3: the overflow property (W3C CSS Working Group)
- CSS Overflow Module Level 4: text-overflow (W3C CSS Working Group)
- flex-wrap (MDN Web Docs) (Mozilla)
- Web features explorer: overflow: clip (W3C WebDX Community Group)
- Web features explorer: flex-wrap: balance (W3C WebDX Community Group)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress