手機上打開自己的專案,發現卡片右半邊被切掉了一塊,程式碼編輯器整個溢出到畫面外。第一直覺是「編輯器沒設好 overflow」,於是往 CodeMirror 上加了各種 class——結果一個都沒用。
真正的兇手在完全不同的地方,而且它是 CSS 裡最經典的陷阱之一:min-width: auto。
TL;DR
flex 和 grid 的子元素,預設 min-width 是 auto 而不是 0。這代表它不會縮到比內容更小。所以只要裡面有一段不換行的長內容,它就會把外層一路往外推,就算你已經寫了 w-full 也一樣。
修法是找出真正被撐大的那一層(往往不是視覺上爆開的那個元件),在它身上加 min-width: 0(Tailwind 的 min-w-0)。
一、症狀
專案首頁有一張卡片,裡面是 CodeMirror 程式碼編輯器。在桌機上一切正常,但用手機開啟、貼進一段比較長的程式碼(例如 Mermaid 的 sequenceDiagram,有些行很長)之後:
- 卡片右側被切掉,超出畫面
- 整個頁面可以左右滑動
- 編輯器的長行沒有在自己的框內捲動,而是直接把卡片撐破
![]()
用 375px(iPhone SE)的視窗實際量:
溢出了 57px。
二、一連串沒有用的修法
因為畫面上「看起來」是編輯器爆出來,我很自然地往它身上加東西:
這裡犯的錯
一直在「看起來壞掉的那個元件」身上找答案。但 CSS 的溢出問題,視覺上爆開的地方,往往不是實際失控的那一層。真正該做的第一件事是把每一層的寬度量出來,而不是猜。
三、關鍵:從內往外量一遍
與其猜,不如直接把從最內層到 <body> 的每一層寬度全部印出來。用 Playwright 跑一次:
const chain = await page.evaluate(() => {
const out = [];
let el = document.querySelector(".cm-content"); // 從最內層開始
while (el && el !== document.body) {
const cs = getComputedStyle(el);
const r = el.getBoundingClientRect();
out.push({
cls: el.className.toString().slice(0, 40),
w: Math.round(r.width),
scrollW: el.scrollWidth,
minW: cs.minWidth, // ← 重點看這個
overflowX: cs.overflowX,
});
el = el.parentElement; // 一路往外走
}
return out.reverse(); // 反轉成由外到內
});結果一目了然(由外到內):
w= 375 min=0px ← body 下的容器,正常
w= 375 min=0px ← 根層的 grid,正常
w= 432 min=auto ← ⚠️ 就是這一層!被推到 432px
w= 384 min=auto ← 卡片外層(w-full max-w-sm)
w= 384 min=0px ← Card
w= 352 min=0px ← Tabs
w= 352 min=0px ← 表單容器
w= 350 min=0px ← .cm-editor
w= 350 sw=508 ← .cm-scroller(有正常捲動)
w= 473 min=auto ← .cm-content(長行的真實寬度)編輯器內部其實是正常的——.cm-scroller 自己寬 350px、內容 508px,水平捲動有在運作(這是前面第 ③ 步的功勞)。
真正失控的是那個 w=432 的頁面外層:它的父層只有 375px,它卻長到 432px。從它以下每一層都被連帶撐開,卡片因此變成 384px,超出畫面。
四、根因:min-width: auto
那一層的 class 是這樣的:
<div class="flex min-h-svh w-full items-center justify-center p-6">它是外層 grid 的子元素(grid item)。這裡有一條很多人不知道的規則:
flex item 和 grid item 的
min-width/min-height,初始值是auto,不是0。
在auto之下,元素不會被壓縮到小於它內容的最小尺寸。
也就是說,就算你寫了 w-full(width: 100%),只要裡面有一段不肯換行的內容,min-width: auto 就會推翻這個寬度,把元素撐到內容需要的大小。
算一下數字剛好對上:
卡片 max-w-sm = 384px
外層 p-6 左右內距 = 24 × 2 = 48px
─────────────
總計 432px ← 完全吻合量到的數字整條因果鏈長這樣:
那 overflow: hidden 到底行不行?
我實測過:在同一層(那個 432px 的元素)改用 overflow-x: hidden,畫面確實也不溢出了,數字跟 min-w-0 一模一樣。所以它不是「無效」。
差別在於做的事情不同:
min-width: 0→ 讓元素縮回它該有的大小(432px → 375px)overflow-x: hidden→ 元素還是 432px,只是超出的部分被裁掉不給看
前者解決成因,後者遮蔽結果。之所以選前者,是因為被裁掉的內容依然佔著空間、只是看不見,之後很容易衍生「明明有東西卻點不到 / 捲不到」的問題。而且裁切點放錯一層,就會連該顯示的內容一起吃掉——我在第 ④ 步就是這樣,把該捲動的區域也裁了。
五、修法
在被撐開的那一層加上 min-width: 0:
function HomeComponent() {
return (
<div className="flex min-h-svh w-full items-center justify-center p-6">
<UploadPanel />
</div>
);
}一個 class,就這樣。 修完再量一次:
375px 視窗、375px 頁面寬度,完全吻合,沒有任何水平溢出。卡片也回到正常的 327px:
![]()
長行沒有消失,它改成在編輯器自己的框裡水平捲動,這才是原本就該有的行為。
六、怎麼判斷該在哪一層加 min-w-0
不用猜,把上面那段量寬度的程式碼貼進瀏覽器 Console 就行(把 .cm-content 換成你那個爆出來的元素):
let el = document.querySelector("你的元素選擇器");
while (el && el !== document.body) {
const cs = getComputedStyle(el);
const w = Math.round(el.getBoundingClientRect().width);
console.log(w, cs.minWidth, el.className.toString().slice(0, 50));
el = el.parentElement;
}
console.log("viewport:", innerWidth, "頁面:", document.documentElement.scrollWidth);判讀方式很簡單:
找出兇手的規則
由外往內看,找出第一個「寬度大於它父層」的元素。如果它的 min-width 是 auto,而且它是 flex 或 grid 的子元素——就是它。在它身上加 min-w-0。
七、教訓
幾個帶得走的重點
- flex / grid 子元素的
min-width預設是auto,不是0。這是絕大多數「莫名其妙水平溢出」的根源。同理還有min-height: auto造成的垂直溢出。 w-full不保證元素不會變寬。min-width: auto的優先度比width高,內容撐得動它。- 視覺上爆開的元件,通常不是失控的那一層。 與其在它身上一直加 class,不如從內往外把每一層的寬度和
min-width印出來,30 秒就能定位。 overflow: hidden能蓋掉症狀,但蓋的不是同一件事。 它讓元素維持過大的尺寸、只是裁掉看不到的部分;min-width: 0才是讓元素縮回正確大小。能修成因就別只遮結果。- 需要讓內層自己捲動(例如程式碼編輯器、表格)時,記得整條鏈上的每一層都要能縮——中間任何一層漏掉
min-w-0,捲動就不會生效。