Flex Item Overflows Container? Fix min-width: auto

A flex child won't shrink and text-overflow: ellipsis silently does nothing? It's the automatic minimum size (min-width: auto) — here's the fix.

css flexbox css-grid, min-width text-overflow css-layout responsive-design frontend
Bharath
Reading Progress

On This Page

Flex Item Overflows Container? Fix min-width: auto

The Symptom

You've built a flex row — a sidebar item, a table-like row, a card header — with a long unbreakable string in one of the children (a filename, an email address, a URL). You add the textbook truncation recipe:

css

.label {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

Nothing happens. The text keeps running past its box, pushing sibling elements out of the way or blowing out past the container's right edge, and the ellipsis never appears. Sometimes it's not text at all — it's an image, a <pre> block, or a nested flex/grid container that refuses to shrink no matter how small you make flex-shrink or how tight the parent gets.

Open DevTools and it gets more confusing. The Styles pane shows text-overflow: ellipsis applied, not struck through. Nothing is being overridden. Flip to the Computed pane and check the item's actual rendered width — it's wider than the flex container allows, and in the Layout overlay the flex item is visibly overflowing its line box. The offending property isn't visible in your stylesheet at all: it's the default value of min-width, and by default that default is auto, not 0.

This happens identically in Chrome, Edge, Firefox, and Safari (desktop and iOS) — it isn't a browser bug, it's the flexbox specification working as designed. The exact same failure shows up in CSS Grid with grid-template-columns: repeat(3, 1fr): a column that "should" be flexible refuses to shrink below its content and blows out the layout, because a bare 1fr track has the same automatic minimum built in.

How to Reproduce It

The version of this bug that actually bites in production almost never has the truncation CSS sitting directly on the flex item — it sits on a nested element, because real components wrap text in a <span>, a design-system Text/Label component, or a table-cell inner <div>. That distinction matters, and it's worth reproducing precisely rather than from memory: save this as repro.html and open it directly in a browser (verified in Chromium via a headless render check before publishing — see the measurements below).

html

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Flex truncation repro</title>
<style>
  .row {
    display: flex;
    gap: 12px;
    width: 320px;
    border: 2px solid #b2452b;
    padding: 8px;
    align-items: center;
  }
  .icon {
    flex: 0 0 auto;
    width: 32px;
    height: 32px;
    background: #3b4cca;
    border-radius: 6px;
  }
  /* the flex item: no overflow set here — this is the wrapper a component library gives you */
  .label {
    flex: 1 1 auto;
    background: #e2e5fb;
  }
  /* the truncation CSS lives on a child, not on the flex item itself */
  .label__text {
    display: block;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
  }
</style>
</head>
<body>
  <div class="row">
    <div class="icon"></div>
    <div class="label">
      <span class="label__text">a-very-long-unbreakable-filename-report-q3-final-v2-FINAL.pdf</span>
    </div>
  </div>
</body>
</html>

Result, measured directly (element.clientWidth / scrollWidth, not eyeballed): the .row is declared at content-box 320px (336px including padding), but .label renders at its full content width — 418px — and the row's scrollWidth comes out to 470px against a 336px clientWidth: real, measurable overflow, not a rendering illusion. The inner .label__text never gets a chance to clip anything (clientWidth and scrollWidth are identical on it) because its containing block, .label, never shrank.

Add one declaration to .label — the flex item, not the text element:

diff

 .label {
   flex: 1 1 auto;
+  min-width: 0;
   background: #e2e5fb;
 }

Reload (or re-run the same measurement): .label now shrinks to its fair share of the row (276px in this layout), the row's scrollWidth drops to match its clientWidth exactly — no more overflow — and .label__text now has clientWidth: 276 against scrollWidth: 418, which is precisely the condition text-overflow: ellipsis needs to render the …. Nothing else in the file changed; no build step, no framework, no polyfill required.

Nuance worth knowing before you call this "browser inconsistency": put overflow: hidden directly on the flex item itself (no wrapper), and the same repro doesn't reproduce — the item shrinks correctly with no explicit min-width: 0. Not luck: any overflow value other than visible/clip makes an element a scroll container, and the spec's automatic-minimum-size rule resolves to 0 for scroll containers instead of min-content. That's why some Stack Overflow answers swear overflow: hidden alone fixed it while others insist you also need min-width: 0 — both are true, for two different DOM shapes. Any wrapper between the flex/grid item and the element carrying overflow: hidden breaks the carve-out, so treat min-width: 0 as required regardless of what your current markup happens to get away with.

The Grid twin, reproduced the same way — a grid item with no overflow set, wrapping an inner block that carries the truncation CSS:

html

<div style="display:grid; grid-template-columns: repeat(3, 1fr); width: 320px; gap: 8px;">
  <div><div style="overflow:hidden; white-space:nowrap; text-overflow:ellipsis;">a-very-long-unbreakable-filename-report-q3-final.pdf</div></div>
  <div><div>two</div></div>
  <div><div>three</div></div>
</div>

Measured: container scrollWidth (416px) exceeds its clientWidth (320px) — the grid genuinely blows out — and the first track sits at 344px instead of its fair ~101px share. Swap grid-template-columns: repeat(3, 1fr) for grid-template-columns: repeat(3, minmax(0, 1fr)) and, measured again: no more overflow (scrollWidth matches clientWidth), the first track shrinks to 101px, and its inner element now clips and truncates correctly.

No toolchain is required to see this — it's pure CSS — but it shows up constantly inside Tailwind (flex-1 maps to flex: 1 1 0%, which still needs min-w-0 on the same element, or on whichever ancestor is the actual flex/grid item), Bootstrap 5 flex utilities, and any component library's row/card/table-cell layout, so if you're debugging it inside one of those, the fix is the same declaration wherever the flex/grid item actually is in your markup — which is often one level higher than the element you first suspect.

Browser & Baseline Support Matrix

This is not a moving-target compatibility question — the automatic minimum size for flex and grid items has been implemented identically across engines for a decade, and min-width: 0 as the override has never needed a prefix or a polyfill.

EngineAutomatic min-size behavior presentmin-width: 0 / minmax(0, 1fr) fix worksNotes
Chrome / Edge (Blink)Yes, since Chrome 29 (2013)YesNo known divergence
Firefox (Gecko)Yes, since Firefox 20s eraYesMatches spec exactly
Safari desktop (WebKit)YesYesNo known divergence
Safari iOS (WebKit)YesYesSame engine as desktop Safari — no separate quirk here

min-width, flex-shrink, text-overflow, and CSS Grid minmax() are all Baseline: Widely available (all supported since 2015–2017 at the latest). There is no @supports feature query that meaningfully detects "does the automatic minimum apply here" — this is universal, spec-mandated behavior, not a feature you can progressively enhance around. The one genuinely newer wrinkle worth checking on webstatus.dev before you rely on it in production is overflow: clip as an alternative to overflow: hidden (Baseline: widely available since 2023, but a scroll-container edge case differs slightly from hidden in older Safari versions) — for straightforward truncation, overflow: hidden remains the safe default across all four rows above.

Why It Happens — Surface Level

Every flex and grid item ships with min-width: auto (or min-height: auto in a column flex container) as its initial value — you never wrote it, so it never shows up in your stylesheet or in the Styles pane as something to override. auto here doesn't mean "no minimum." For a flex or grid item, auto resolves to the item's min-content size — roughly, the smallest size the content can be without overflowing or breaking mid-word. For a text node, that's the width of its single longest unbreakable run of characters (a filename with no spaces, a URL, a long word).

flex-shrink: 1 (the default) tells the browser "this item is allowed to get smaller than its flex-basis," but it does not override the floor set by the automatic minimum. The item shrinks only until it hits min-width: auto's computed value, then stops shrinking and overflows the container instead. Whatever element down the tree carries overflow: hidden + text-overflow: ellipsis never gets a chance to clip anything, because its containing block — the flex/grid item — never became smaller than its content in the first place. There's nothing to clip.

Why It Happens — Under the Hood

The flexbox layout algorithm computes each item's hypothetical main size from flex-basis (falling back to the item's own width/content when flex-basis: auto), then distributes free space among items via flex-grow/flex-shrink, clamping each item's resulting size against its min/max constraints. Per the CSS Flexible Box spec, that clamp uses the item's minimum size in the same axis — and an unset minimum is auto, which resolves to min-content rather than 0, specifically so a flex item's contents aren't forced to unreadable sizes by default. Shrink factors get applied, but the result is always clamped up to at least min-content before the item is placed.

min-width: auto collapses to 0 only in two situations the spec carves out explicitly: when the flex/grid item is itself a scroll container (i.e., it has overflow: hidden|scroll|auto set directly on the item — confirmed in the repro above: a flex item with overflow: hidden on itself shrinks correctly with no extra declaration, because the carve-out already zeroes its automatic minimum), or when a grid item spans more than one flexible (fr) track. The moment a wrapper sits between the flex/grid item and whichever descendant actually carries overflow: hidden, the carve-out has nothing to attach to — the item itself has overflow: visible (the initial value), so it's never a scroll container, and its automatic minimum stays at min-content. Setting min-width: 0 explicitly reproduces the carve-out's effect manually, on any element, regardless of whether it happens to also be a scroll container — which is why it's the fix that works in every case, not just the lucky one.

Grid's version of the same mechanism is why a bare 1fr track famously behaves like minmax(auto, 1fr) rather than minmax(0, 1fr): the track-sizing algorithm resolves each track's base size from its content's min-content contribution before distributing flexible fr space, and auto as the floor of that minmax() is the automatic-minimum-size rule again, just expressed as a track constraint instead of an item property. minmax(0, 1fr) replaces that implicit auto floor with an explicit 0 — the identical fix as min-width: 0 on a flex child, not a separate Grid-specific quirk.

Watch it live in DevTools: read getComputedStyle(el).minWidth on the offending item in the console — it reports auto as the specified value but resolves to a real pixel number (the content's min-content width) in the Computed pane. That's the tell distinguishing this from an ordinary "my rule got overridden" cascade bug: nothing was overridden, a value you never set is doing the clamping.

The Fix

Quick fix — override the automatic minimum directly on the flex/grid item (this is the correct fix, not a hack — apply it whether or not the item also happens to carry overflow: hidden itself):

diff

 .label {
   flex: 1 1 auto;
+  min-width: 0;
   background: #e2e5fb;
 }

For a column flex container (items stacking vertically, overflowing height instead of width), the equivalent is min-height: 0 on the item.

Grid equivalent:

diff

 .grid {
   display: grid;
-  grid-template-columns: repeat(3, 1fr);
+  grid-template-columns: repeat(3, minmax(0, 1fr));
 }

If the item is nested (a flex child that is itself a flex or grid container with its own overflowing child), the min-width: 0 override has to be applied at every level in the chain where a flex/grid item sits between the container and the content that needs to shrink — it does not propagate down automatically. This is the single most common reason "I already added min-width: 0 and it's still broken" reports happen: the fix was applied one level too shallow.

What doesn't actually fix it, and what it costs:

  • width: 0 or a fixed pixel width on the label — works by accident at one viewport size, breaks the truncation threshold at every other size, and fights the whole point of using flex for fluid layout.
  • overflow-x: hidden on a grandparent instead of the item itself — clips visually but doesn't touch the min-content floor on the actual flex item, so intermediate flex/grid children in between can still blow out and cause horizontal scrollbars elsewhere in the tree.
  • word-break: break-all as a substitute — genuinely changes the rendered content (breaking mid-word) rather than truncating it; reach for this only when you want wrapping behavior, not ellipsis behavior.
  • Adding overflow: hidden only to the inner text element and calling it done — as the repro above shows, that clips nothing on its own when a plain (non-scroll-container) wrapper sits between it and the flex/grid item; the automatic minimum tracks whichever element is the direct flex/grid child, not whichever element happens to carry overflow: hidden.

Best Practices & The Better Design

Treat min-width: 0 (and min-height: 0 for column flex, and minmax(0, 1fr) for grid tracks) as a default you add to every flex/grid item that is allowed to contain unpredictable or user-generated content — a label, a table cell, a chat bubble, a breadcrumb segment — rather than something you discover per-bug. A small reusable utility class or a CSS custom property-driven mixin keeps this from becoming tribal knowledge:

css

/* apply to any flex/grid item that must be allowed to shrink below its content */
.shrinkable {
  min-width: 0;
  min-height: 0;
}

html

<div class="row">
  <div class="icon"></div>
  <div class="label shrinkable">a-very-long-unbreakable-filename-report-q3-final-v2-FINAL.pdf</div>
</div>

If you're on Tailwind, this is exactly what its own min-w-0 utility exists for — pair it with flex-1 (which already sets flex-basis: 0%) rather than reaching for arbitrary pixel widths. The general principle behind the fix generalizes past this one bug: let content report its own natural size and let the layout algorithm (flex, grid) do the arithmetic, rather than hard-coding widths anywhere in the chain — a hard-coded width is what this whole failure mode teaches people to reach for next, and it's the wrong lesson.

How to Prevent It Long-Term

Add declaration-property-value-no-unknown and a documented lint rule (or a code-review checklist item) requiring min-width: 0 / min-height: 0 on any flex/grid item wrapping dynamic text, and bake minmax(0, 1fr) into your grid mixins/tokens instead of ever hand-typing a bare 1fr for content columns. Add a visual regression test (Playwright screenshots across Chromium/Firefox/WebKit projects, or Chromatic/Percy) with a deliberately long, unbreakable string as test fixture data in every row/card component — this class of bug is invisible with short lorem-ipsum test content and only appears with real-world data (long filenames, emails, URLs), which is exactly why it reaches production. A Lighthouse/CLS budget won't catch this on its own since the failure is horizontal overflow, not shift, so pair the visual regression check with a simple "no element wider than its parent" assertion in your component tests if your test runner supports layout assertions.

Key Takeaways

  • Every flex and grid item has an invisible default of min-width: auto (or min-height: auto in column flex) — it resolves to the content's min-content size, not to 0, and it silently overrides flex-shrink and any truncation CSS you add.
  • text-overflow: ellipsis requires the box to actually become smaller than its content first; if min-width: auto is stopping that from happening, the ellipsis has nothing to clip and never appears.
  • The fix is one declaration — min-width: 0 (flex) or minmax(0, 1fr) in place of a bare 1fr (grid) — applied at every nested flex/grid level between the container and the overflowing content, not just the outermost one.
  • This is universal, decade-old, spec-mandated behavior in Chrome, Firefox, and Safari alike — there's no browser bug to blame and no polyfill needed, only a default value that's easy to forget because you never typed it.
  • Neighboring concepts worth knowing: this is the same underlying "automatic minimum size" mechanism referenced when a 1fr grid track won't shrink, and it's a different root cause from the overflow: hidden-on-an-ancestor clipping problem covered separately for position: sticky and dropdown/tooltip clipping — don't confuse a min-content floor with an overflow-clipping ancestor when debugging.
cssflexboxcss-grid,min-widthtext-overflowcss-layoutresponsive-designfrontend

Engineer and writer behind CODELZ. I read the stack trace, reproduce the bug, and explain why it happens, not just how to silence it. Plus honest reviews of the books and tools worth your time.

Comments