Skip to content
R.

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.

About 5 min readComments

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.

Baseline fixture / HTML and CSS
<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.

Repair / CSS
.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
Browser test / JavaScript
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.

When `min-height: 0` helps and when another fix is needed
CaseWhat failsWhat to change
Scrollable flex columnThe document scrolls instead of the paneInspect the nested flex-item chain; this fixture sets `min-height: 0` on both `.workspace` and `.mailpane`
Bad upstream heightThe container never gets a usable heightGive the ancestor chain a definite height first
Grid overflowTrack sizing still forces the content wider or tallerUse the grid-specific minimum-size fix instead

SourcesCSS Flexible Box Layout Module Level 1 (opens a new tab)

Share LinkedIn Email Subscribe

Discussion

Leave a comment

Comments appear after review. No email needed.

Image preview

Follow the blog

New articles in your feed. No email needed.

Use OpenRSS

Preview the feed, then choose a reader to subscribe.

Open in OpenRSS (opens a new tab)

Already have a reader?

Paste this link into your reader's Add feed option.

Get article summaries in your reader, not your inbox. View XML feed