Skip to content

你寫了 display: flex,瀏覽器偷偷塞了五個值給你

你大概常寫這種程式碼:把三個 div 包在一個 container 裡,加一句 display: flex,東西就乖乖橫向排好了。

html
<div class="container">
  <div class="box">A</div>
  <div class="box">B</div>
  <div class="box">C</div>
</div>
css
.container {
  display: flex;
}

順利的話畫面長得跟你期待的差不多,於是你心想「好,搞定」就跳下一個任務了。

但其實,你寫了一行 CSS,瀏覽器幫你寫了一堆。今天把這些「藏起來的預設值」挖出來看清楚 —— 特別是大家最常搞混的 flex-growflex-shrinkflex-basis 三個傢伙。

等等,那三個 box 到底為什麼是那個寬度?

做個實驗。給 container 一個明確寬度,三個 box 都不設寬度:

css
.container {
  display: flex;
  width: 600px;
  border: 2px solid black;
}

.box {
  background: lightblue;
  /* 注意:完全沒設 width */
}

結果你會看到:每個 box 的寬度剛好等於它內容的寬度 —— 字多就比較寬,字少就比較窄。container 剩下的空間?空在那邊沒人要。

奇怪吧?你以為 flex 會自動分配空間,但好像不是?

這是因為瀏覽器其實偷偷給每個 flex item 套了這行 default:

css
.box {
  flex: 0 1 auto;
}

翻成白話:「不主動長大、空間不夠的時候願意縮、預設大小看內容自己決定。」

flex 是一個 shorthand(縮寫屬性),它一次寫三個值,順序固定是:

flex: <flex-grow> <flex-shrink> <flex-basis>

如果你跟我一樣第一次看到這三個名字就頭痛 —— 別急,我們用一個更好的方式來記它。

把它想成一份「彈性合約」

flex-growflex-shrinkflex-basis 這三個名字其實不太直觀。我習慣這樣重新框架它們:

flex: [想長多大] [願意縮多少] [理想起點]

💡 記憶錨點:把 flex: A B C 想成 flex: [max 行為] [min 行為] [起點]

  • A 越大,越會搶剩下來的空間
  • B 越大,越會在擠的時候讓步
  • C 是「如果空間剛剛好,我想要這個大小」

換句話說,這三個值在描述一個 flex item「面對空間變化時的態度」:

  • 空間有剩 → 看 A(grow)來決定怎麼分
  • 空間不夠 → 看 B(shrink)來決定誰要縮
  • 空間剛好 → 大家就維持 C(basis)的大小

OK,有了這個框架,我們一個一個拆開。

flex-grow:「剩下的空間給我多少?」

flex-grow 預設是 0,意思是「就算有剩,我也不主動搶」。

這就是為什麼前面那個例子,三個 box 不會撐滿 container —— 因為它們都是 flex-grow: 0,沒人想要那塊多出來的空間。

來改一下,把第一個 box 設成 flex-grow: 1

css
.box:nth-child(1) {
  flex-grow: 1;
}

現在第一個 box 會把所有剩下的空間吃光,另外兩個維持原本內容寬度。

那如果三個都設 flex-grow: 1 呢?

css
.box {
  flex-grow: 1;
}

直覺以為是「每個一樣寬」對吧?

但這裡有個雷:它們不一定等寬。flex-grow 分的是「剩下來的空間」,不是「整個 container 的空間」。如果 box A 的內容是 Hi,box B 的內容是一整段長文,B 一開始就比較寬 —— 即使你給三個都 flex-grow: 1,B 還是會比較寬,只是「剩餘空間」會被平均分掉而已。

要真的等寬,必須把 flex-basis 一起改。等下會講。

不同數字代表的是「比例」

如果你寫:

css
.box:nth-child(1) { flex-grow: 1; }
.box:nth-child(2) { flex-grow: 2; }
.box:nth-child(3) { flex-grow: 1; }

意思是「剩下的空間切成 4 份(1+2+1),第一個拿 1 份、第二個拿 2 份、第三個拿 1 份」。

注意:這個分配是針對「剩下」的空間,不是整個 container。所以最終寬度不會剛好是 1:2:1。這真的很容易搞混,我自己一開始也卡了好久。

flex-shrink:「擠的時候我願意縮多少?」

flex-shrink 預設是 1,意思是「空間不夠的時候,跟大家一起縮」。

來看一個會擠的場景:

css
.container {
  display: flex;
  width: 300px;
}

.box {
  width: 200px;  /* 三個加起來 600px,明顯放不下 */
}

照理說 200 × 3 = 600,比 container 大兩倍。但你不會看到 box 滿出來 —— 因為 flex-shrink: 1 預設讓它們等比例縮小,最後剛好塞進 300px。

那如果我說「我不要縮」呢?

css
.box:nth-child(1) {
  flex-shrink: 0;  /* 我硬是不縮 */
}

第一個 box 維持 200px,剩下 100px 由另外兩個 box 去分(它們得縮得更兇才塞得下)。

💡 實際用途flex-shrink: 0 在做側邊欄、logo、icon、固定寬度的按鈕時很有用 —— 你不想讓它們在小螢幕被擠扁。

為什麼 grow 預設 0,但 shrink 預設 1?

老實說,這個不對稱一開始很容易搞混。但仔細想想其實合理:

  • 擠不下是嚴重問題,必須有人縮,不然會破版 → 所以預設大家都願意縮(shrink: 1)
  • 空間有剩不是問題,留白也沒關係 → 所以預設沒人主動搶(grow: 0)

換句話說,瀏覽器的預設立場是:「擠的時候大家一起忍耐,剩的時候就留著吧。」

flex-basis:「如果一切剛好,我想要多大?」

flex-basis 是三個裡面最微妙的一個。它是 flex item 在「分配剩餘空間或縮減之前」的起始尺寸

預設值是 auto,意思是「看我自己的內容決定」。

來比較三種寫法,假設 container 寬 600px、三個 box 內容寬度不一樣:

css
/* 寫法 A:看內容 */
.box { flex-basis: auto; }

/* 寫法 B:固定起點 */
.box { flex-basis: 100px; }

/* 寫法 C:從零開始 */
.box { flex-basis: 0; }

差異整理如下:

flex-basis結果
auto每個 box 起點是自己的內容寬度,剩餘空間照 grow 分
100px每個 box 從 100px 開始,剩下 300px 照 grow 分
0每個 box 從 0 開始,整個 600px 照 grow 分

這就解釋了為什麼前面說「三個都 flex-grow: 1 不一定等寬」。要等寬,把 basis 設成 0

css
.box {
  flex: 1 1 0;  /* grow=1, shrink=1, basis=0 */
}

或最常見的縮寫:

css
.box {
  flex: 1;  /* 等同 flex: 1 1 0 */
}

等等,這裡藏了一個雷。下一節說。


但是!flex: 1 跟你以為的不一樣

flex 這個 shorthand 有幾個很容易搞混的「速記值」:

css
.box { flex: 1; }     /* = flex: 1 1 0    ⚠️ basis 是 0,不是 auto! */
.box { flex: auto; }  /* = flex: 1 1 auto */
.box { flex: none; }  /* = flex: 0 0 auto */
.box { flex: 0; }     /* = flex: 0 1 0    ⚠️ */

特別注意 flex: 1flex: auto 的差別 —— 一個 basis 是 0、一個是 auto

  • flex: 1 → 不管內容多寡,大家平均分(因為起點都從 0 算)
  • flex: auto → 起點是內容大小,只有「剩下」的空間才平均分

90% 的人想要的是 flex: 1,但會直覺寫成 flex: auto,結果發現排版怪怪的。

但是!內容大小可以打破這一切

到目前為止我們都假設 flex item 會乖乖照 basis 走。但實際上有個隱藏規則:

flex item 預設不會縮到比它的內容還小。

舉例:

css
.container {
  display: flex;
  width: 300px;
}

.box {
  flex: 1 1 0;  /* 起點 0、可以縮可以長 */
}

照理說,三個都從 0 起跳、grow 1、shrink 1,應該各 100px。但如果其中一個 box 裡有一個超長字串(例如一個沒有空格的 URL,像 https://example.com/very-long-path-without-spaces),那個 box 就會被「撐」到至少能放下那串文字的寬度 —— 即使 shrink 是 1 也壓不下去。

為什麼? 因為 flex item 預設還偷偷有一個屬性 min-width: auto,意思是「最小不能小於內容寬度」。

要解掉這個鎖,加這兩行:

css
.box {
  min-width: 0;       /* 允許縮到比內容還小 */
  overflow: hidden;   /* 配合處理溢出的部分 */
}

這個 min-width: 0 是 flex 排版裡一個著名的「魔法咒語」。第一次遇到溢出問題的人通常會卡很久 —— 因為文件不會主動告訴你還有這個隱藏的 min-width 在搞鬼。老實說,這個設計挺反直覺的。


🐛 常見錯誤與除錯提示

常見錯誤 1:三個 box 都寫 flex-grow: 1 卻不等寬

會在什麼時候出現?

當你想做等寬的網格,給每個 flex item 設 flex-grow: 1,但發現內容多的 box 還是比較寬。

錯誤示範:

css
.box {
  flex-grow: 1;
  /* 沒設 flex-basis,預設是 auto */
}

為什麼?

flex-grow 分的是「剩下」的空間,不是「全部」的空間。如果某個 box 內容比較長,它的初始寬度(auto = 內容寬度)就比較大,加上「平均分配的剩餘空間」之後,總寬度當然還是比較大。

修正:

css
.box {
  flex: 1;  /* = flex: 1 1 0,起點都從 0 算 */
}

把 basis 設成 0,每個 box 從同一個起點開始,剩下的空間才能真正平均切。


常見錯誤 2:長 URL / 長字串把 flex item 撐爆,overflow 沒效果

會在什麼時候出現?

你在做聊天訊息泡泡或卡片,裡面可能出現很長的 URL。你預期超長文字會出 scroll bar 或被截斷,但它卻把整個 box 撐到比 container 還寬。

錯誤示範:

css
.container {
  display: flex;
  width: 400px;
}

.message {
  flex: 1;
  overflow: auto;  /* 期待會出 scroll bar */
}

錯誤現象:

沒有錯誤訊息,就只是看起來怪怪的 —— 整個 container 被內容撐開、把 layout 推爆,你的 overflow 完全沒被觸發。

為什麼會這樣?

因為 flex item 預設有 min-width: auto,最小寬度被綁住不能小於內容寬度。flex 排版會優先把 item 撐到放得下內容為止,根本沒機會輪到 overflow 生效。

修正:

css
.message {
  flex: 1;
  min-width: 0;     /* 解掉隱藏的 min-width: auto */
  overflow: auto;
}

加上 min-width: 0,flex item 才會允許縮到比內容還小,overflow 才會真的生效。


常見錯誤 3:flex: 1flex: auto 搞混

會在什麼時候出現?

你想做平均分配,習慣性寫 flex: auto,結果發現有些 box 寬有些 box 窄。

錯誤示範:

css
.box {
  flex: auto;  /* 你以為這是平均分配 */
}

為什麼?

  • flex: auto 展開是 flex: 1 1 auto(basis 是 auto,看內容)
  • flex: 1 展開是 flex: 1 1 0(basis 是 0,從零起跳)

內容大小不同的時候,這兩個結果完全不一樣。

修正:

css
.box {
  flex: 1;  /* 真的想要平均分配的話用這個 */
}