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% を一行足すと直る。
/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 解決より先に自動マージンが残余空間を消費する。
順を追うと:
flex-direction: columnの親なので、cross 軸 = horizontal- 子
.pageはalign-items: normal(=stretch相当) だが、margin-left: autoとmargin-right: autoが両方ある - 仕様上、auto margin が空間を取り合うため stretch が打ち消される
- その結果、子の幅は
min-content/shrink-to-fitに解決される - 子が 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;
}
順序は:
width: 100%→ 子が 1280px に広がろうとするmax-width: 980px→ 980px にクランプ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の computeddisplay: flex/flex-direction: column.childの computedwidthがmax-widthより小さい.childの computedmargin-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 が出典。)