miya8060
← back to blog

NOTE · 2026-05-07

flex-col 親で子の max-width が効かない: auto margin が stretch を打ち消す

<body class="flex flex-col"> の下に max-width: 980px; margin: 0 auto; だけ指定した <main> を置くと、viewport 1280px で main が 776px に潰れた。CSS Flexbox の cross-axis auto margin が stretch より先に解決される仕様で、子は intrinsic 幅に shrink する。width: 100% を一行足すと直る。

cssflexboxtailwindlayout

/notebooks 一覧をリデザインしていて、grid が 3 列のはずが 2 列に潰れる崩れを目視で見つけた。原因は flexbox の auto margin と stretch の解決順で、width: 100% を一行足すと直る単純な話なのだが、pattern として刺さる頻度が高そうなので残しておく。block formatting context の感覚で書いた CSS が flex parent の下では通用しないという話。

何が起きたか

レイアウトはこうなっていた:

// app/layout.tsx (抜粋)
<body className="flex min-h-full flex-col">
  {/* ... */}
  <main className={styles.page}>{children}</main>
  {/* ... */}
</body>
/* notebooks.module.css */
.page {
  max-width: 980px;
  margin: 0 auto; /* 中央寄せのつもり */
  padding: 36px 28px 120px;
  /* width 指定なし */
}

期待: viewport 1280px で .page は 980px 幅、margin: 0 auto で中央寄せ、中の grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)) が 3 列で並ぶ。

実際: .page は中身の合計幅 (今回は 776px) に縮み、grid が 2 列になっていた。devtools で .page の computed width を見ると 776px、min-content / shrink-to-fit に解決されている。

なぜそうなるか

CSS Flexbox 仕様 (W3C Flexbox Level 1 §8.1 "Aligning with auto margins") で、flex item の cross 軸に auto margin があると、stretch 解決より先に自動マージンが残余空間を消費する

順を追うと:

  1. flex-direction: column の親なので、cross 軸 = horizontal
  2. .pagealign-items: normal (= stretch 相当) だが、margin-left: automargin-right: auto が両方ある
  3. 仕様上、auto margin が空間を取り合うため stretch が打ち消される
  4. その結果、子の幅は min-content / shrink-to-fit に解決される
  5. 子が intrinsic に潰れているので、max-width: 980px でクランプする以前に問題が解決済み (max-width は何もしない)

ブロック要素の通常フロー (block formatting context) では width: auto + margin: 0 auto で親いっぱいに広がってから auto margin で中央寄せされる。ところが flex parent ではこれが通用しない。同じ書き方が違う結果を出すので、書いた人 (= 私) は混乱する。

直し: width: 100% を一行足す

.page {
  width: 100%; /* 明示すると親いっぱいに広がろうとする */
  max-width: 980px; /* それを 980px でクランプ */
  margin: 0 auto; /* 残った余白を auto margin で中央寄せ */
  padding: 36px 28px 120px;
}

順序は:

  1. width: 100% → 子が 1280px に広がろうとする
  2. max-width: 980px → 980px にクランプ
  3. margin: 0 auto → 残った 300px を左右に等分

box-sizing: border-box が前提だが、Tailwind v4 や普通の globals.css ではすでに global 適用済みなので、ここを気にする必要は基本ない。

効かない代替案

  • 親の flex を block に戻す (<body class="flex flex-col"><body>): 他の flex 子要素がいないなら一番シンプル。今回は header / footer も含めて column flex で並べる前提だったので不採用
  • 親に align-items: stretch明示 しても解決しない。仕様上 auto margin が優先される
  • 子に align-self: stretch を書いても同上で効かない
  • 子を flex container にして margin-inline: auto する解もある。width: 100% の方がシンプルでこちらは不要

識別の手がかり

devtools で:

  • .parent の computed display: flex / flex-direction: column
  • .child の computed widthmax-width より小さい
  • .child の computed margin-left / margin-right が px 単位 (auto 解決後の値、auto ではなく具体的な数値が入っている)

width: 100% を一行足して直るなら確定。15 秒で判定できる。

なぜここで効くか

App Router で <body class="flex flex-col"> はすごくよく使う pattern (sticky footer や min-h-screen の page を書くときの定番)。その下に max-width: <num>; margin: 0 auto の content container を置くのも定番。この 2 つを組み合わせた瞬間にこの罠が立ち上がる ので、Tailwind + Next.js App Router で書いている人は遠からず踏む。width: 100% を一行足す癖をつけると、container の width が想定通りに振る舞う。

(Origin: kokan-nikki の /notebooks リデザイン PR #85、commit 2a33153 で fix。<body class="flex min-h-full flex-col"> の下に置いた <main className={styles.page}> が viewport 1280px で 776px に潰れ、cards grid が 2 列になっていた問題。MDN の Aligning items in a flex container — Using auto margins と W3C Flexbox §8.1 が出典。)