Why does a scrollable flex column need `min-height: 0`?
A nested message pane stays too tall until the flex item can shrink below its content. This fixture shows the overflow failure and the repaired scroll boundary.
The layout already has a header and a message list, but the list refuses to become the scrollable region. Instead, the page grows and the browser scrolls the whole document.
Reproduce the overflow in a mailbox shell
The controlled fixture uses a fixed-height app shell with a header and a long message list. Baseline CSS gives the list `overflow: auto`, but the surrounding flex item still keeps its automatic minimum height.
That means the list can stay taller than the viewport even when it looks like it should be the scroller. The browser scrolls the page instead of the pane.
<main class="app">
<header class="topbar">
<strong>Inbox</strong>
<button type="button" data-fix>Apply min-height: 0</button>
</header>
<section class="workspace">
<section class="mailpane" aria-label="Message list">
<div class="mailpane__header">Today</div>
<ul class="messages">
<li>Confirm delivery window</li>
<li>Quarterly summary ready to review</li>
<li>One long subject line keeps going to trigger the failure</li>
<li>Another message</li>
<li>More content keeps the pane long enough to overflow</li>
</ul>
</section>
</section>
</main>
<style>
html, body { height: 100%; margin: 0; }
body { font: 16px/1.4 system-ui; }
.app { display: flex; flex-direction: column; height: 100vh; }
.topbar { flex: 0 0 auto; padding: 1rem 1.25rem; }
.workspace { flex: 1 1 auto; display: flex; flex-direction: column; }
.workspace.fixed { min-height: 0; }
.mailpane { flex: 1 1 auto; overflow: auto; }
.mailpane.fixed { min-height: 0; }
.messages { margin: 0; padding: 1rem; }
</style> SourcesCSS Flexible Box Layout Module Level 1 (opens a new tab)min-height - CSS | MDN (opens a new tab)
Remove the automatic minimum size
This fixture has two nested flex items: `.workspace` sits below the header, and `.mailpane` sits inside `.workspace`. The tested repair applies `min-height: 0` to both. In particular, the non-scrolling `.workspace` ancestor must be allowed to shrink; changing only the inner pane can leave that ancestor holding the page open. The pane keeps `overflow: auto` and is given an explicit zero minimum in the tested CSS.
Once the minimum is gone, the pane can become shorter than its list and the list's own overflow handling takes over.
.workspace {
display: flex;
flex: 1 1 auto;
flex-direction: column;
min-height: 0;
}
.mailpane {
flex: 1 1 auto;
min-height: 0;
overflow: auto;
} SourcesCSS Flexible Box Layout Module Level 1 (opens a new tab)
Prove the scroll boundary with geometry assertions
The local test reads the same fixture twice: once before the repair and once after it. Before the fix, the document scroll height exceeds the viewport. After the fix, the inner pane is the scroller and the document no longer grows.
That checks the behavior directly instead of inferring it from a screenshot alone.
Browser test
import path from 'node:path';
import { pathToFileURL } from 'node:url';
const { withArticleBrowser } = await import(
pathToFileURL(path.resolve('hosting/scripts/daily-blog-browser.mjs'))
);
await withArticleBrowser(async page => {
await page.goto('file://' + path.resolve('.firebase/daily/examples/flex-min-height-zero.html'));
const before = await page.evaluate(() => ({
doc: document.scrollingElement.scrollHeight,
viewport: document.scrollingElement.clientHeight,
pane: document.querySelector('.mailpane').scrollHeight,
}));
if (before.doc <= before.viewport) throw new Error('expected the baseline to overflow the document');
await page.locator('[data-fix]').click();
const after = await page.evaluate(() => ({
doc: document.scrollingElement.scrollHeight,
viewport: document.scrollingElement.clientHeight,
pane: document.querySelector('.mailpane').scrollHeight,
paneClient: document.querySelector('.mailpane').clientHeight,
}));
if (after.doc > after.viewport) throw new Error('expected the repaired layout to keep the document within the viewport');
if (after.pane <= after.paneClient) throw new Error('expected the message pane to remain scrollable');
}); SourcesCSS Flexible Box Layout Module Level 1 (opens a new tab)
Know the boundary of this fix
This change solves the flex-item minimum-size problem. It does not repair a bad upstream height, a grid track that still refuses to shrink, or content that needs its own overflow rule.
If the ancestor chain never gives the flex container a definite height, `min-height: 0` on the child cannot invent one.
| Case | What fails | What to change |
|---|---|---|
| Scrollable flex column | The document scrolls instead of the pane | Inspect the nested flex-item chain; this fixture sets `min-height: 0` on both `.workspace` and `.mailpane` |
| Bad upstream height | The container never gets a usable height | Give the ancestor chain a definite height first |
| Grid overflow | Track sizing still forces the content wider or taller | Use the grid-specific minimum-size fix instead |
SourcesCSS Flexible Box Layout Module Level 1 (opens a new tab)
