筆記本的方格紙上,每個字、每條線都有自己的位置,想把標題跨三格、側邊留一欄,沿著格線畫就好。CSS 的 grid 做的正是這件事。grid 原意是格子、網格,grid中文常見的譯法是格線或網格(MDN 繁中術語頁譯為「格線」),而在 CSS 裡,它指用橫線與直線切出列與欄的二維版面系統。
在 Grid 出現之前,多欄版面得靠 float、inline-block 或表格硬湊,等高欄位、跨欄標題、頁首側欄頁尾這些再普通不過的版型,都要多包幾層元素、多寫幾行補丁。Grid 把這些需求收進同一套屬性,先決定欄與列怎麼切,再決定每個項目占幾格,版面就定下來了。
讀完這篇,你可以照著貼上程式碼,在瀏覽器裡看到 3 欄排列、fr 比例分配、頁首側欄頁尾版型,以及不寫任何斷點、欄數會隨視窗寬度自己增減的卡片牆。而這一切的起點,只有父元素上的一行宣告。
宣告 Grid 容器後,直接子元素自動成為格子裡的項目
要讓一個元素變成格線版面,只需要在它身上寫 display: grid。這個元素就成為 grid 容器,它的直接子元素全部自動變成 grid item,也就是格子裡的項目;孫層以下的元素不參與,仍照原本的規則排列。
下面這份 HTML 是整篇文章反覆使用的起手檔,包含一個 .wrapper 容器和 6 個 .item,.item 加上背景色、內距與圓角,格子才看得見。存成 grid.html 後用瀏覽器開啟即可。
<!DOCTYPE html>
<html lang="zh-Hant">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>CSS Grid 練習</title>
<style>
body {
font-family: sans-serif;
margin: 24px;
}
.wrapper {
display: grid;
}
.item {
background: #4f7cff;
color: #fff;
padding: 16px;
border: 2px solid #fff;
border-radius: 8px;
}
</style>
</head>
<body>
<div class="wrapper">
<div class="item">1</div>
<div class="item">2</div>
<div class="item">3</div>
<div class="item">4</div>
<div class="item">5</div>
<div class="item">6</div>
</div>
</body>
</html>Code language: HTML, XML (xml)
此時 .wrapper 只有 display: grid,瀏覽器會把 6 個 .item 疊成單欄,由上到下各占一列、寬度都撐滿容器,畫面上看起來和普通的區塊元素差不多。原因在於還沒有定義欄,Grid 預設只建出一欄。
另一個值 display: inline-grid 的差別只在容器本身。grid 的容器像區塊元素一樣獨占一行,inline-grid 的容器則像行內元素,可以和旁邊的文字並排,兩者裡面的格線規則完全相同。
後面各節的程式碼,除非另有交代,都是「把 .wrapper 那段規則換成新的寫法」,HTML 維持這份不動。想看格線實際長什麼樣子,可以在 Chrome 開發人員工具的 Elements 面板選取 .wrapper,元素旁會出現 grid 徽章,點下去就會把格線畫在頁面上,改完程式碼重新整理,也能馬上對照。
格線、軌道、儲存格、區域與間距這 5 個名詞
Grid 有一組固定名詞,弄清楚之後,才讀得懂「從第 2 條線跨到第 4 條線」「橫跨兩個軌道」這類說法。
- 格線(line):構成版面的水平線與垂直線。4 欄的版面有 5 條欄線,線號從 1 起算;在英文這類由左到右的語言裡,第 1 條欄線在最左邊,由右到左的語言則在最右邊。
- 軌道(track):兩條相鄰格線之間的空間,分成欄軌與列軌。一欄就是一條欄軌,一列就是一條列軌。
- 儲存格(cell):欄軌與列軌交會出的最小單位,概念和表格的儲存格相同。
- 區域(area):由多個儲存格合併成的範圍,形狀一定是矩形,L 形的區域做不出來。
- 間距(gap):軌道與軌道之間的空隙。計算尺寸時它和一般軌道一樣占空間,但不能放內容,只有項目可以跨過它。
可以把它想成一張 4 欄 2 列的課表,每一格是儲存格,分隔線是格線,同一門課橫跨兩欄的那塊就是區域,格與格之間的留白則是間距。

名詞對齊之後,Grid 與 Flexbox 的分工也能用一句話說清楚。W3C 的 CSS Grid Layout 規格指出,Grid 是為二維版面最佳化的版面模型,Flexbox 則以單一軸向為主,所以同時要管欄又要管列的版面交給 Grid,單排排列的元件交給 Flexbox。
grid-template-columns 決定欄數與欄寬
grid-template-columns 是真正開始排格子的屬性,寫了幾個尺寸就有幾欄,每個尺寸就是那一欄的寬度。尺寸可以混用 px、em、% 等單位,也可以寫 auto,讓那一欄依內容的大小決定寬度。
列的做法對稱,用 grid-template-rows 定義列高,例如 grid-template-rows: 100px 100px 會明確定義出兩列,各 100px 高。只要欄或列是用 grid-template-* 寫出來的,這部分就叫明確格線,沒寫的部分則交給瀏覽器處理。
固定 200px 排出 3 欄,視窗太窄會出現橫向捲軸
把 .wrapper 那段規則換成下面這段,就是 3 欄、每欄 200px:
.wrapper {
display: grid;
grid-template-columns: 200px 200px 200px;
}Code language: CSS (css)
瀏覽器會渲染出 3 欄,每欄 200px 寬,6 個 .item 由左到右、由上到下依序填入,排成 2 列、每列 3 個;列高由內容決定。項目數量湊不滿最後一列時,最後一列就只有前幾格有內容。
固定寬度的缺點也很明顯,3 欄加起來就是 600px,視窗窄於約 620px 時,格線會超出頁面、出現橫向捲軸。想讓欄寬跟著容器伸縮,要換成下一個單位 fr。

隱含格線自動補上的列,由 grid-auto-rows 統一高度
上面的例子只寫了 grid-template-columns,沒有任何地方告訴瀏覽器要幾列,但畫面上還是排出了 2 列。這些由瀏覽器依內容多寡自動補上的列,叫隱含格線,列高預設是 auto,也就是內容多高、那一列就多高。
這個預設很容易讓列高失控,某個項目放進幾行文字,那一列就被撐高。想統一高度,用 grid-auto-rows 指定自動補上的列要多高:
.wrapper {
display: grid;
grid-template-columns: 200px 200px 200px;
grid-auto-rows: 120px;
}Code language: CSS (css)
畫面上同樣是 3 欄 2 列,不同的是兩列現在都是 120px 高。如果項目增加到 9 個,瀏覽器就會再補上第 3 列,高度一樣是 120px。grid-template-rows 與 grid-auto-rows 分工不同,前者只管明確寫出來的那幾列,後者管之後多出來的所有列。像「最少 100px、內容多就長高」這種彈性寫法,則要搭配後面會講到的 minmax()。
fr 單位依比例分配剩餘空間
fr 是 Grid 專用的彈性單位,W3C 規格把它定義為格線容器剩餘空間的一個比例份額。不少人看到 1fr 2fr 會直覺想成百分比,其實它切的是扣掉固定寬度與間距之後剩下的空間,不是整個容器。
最單純的用法是三欄等寬。以容器寬度 1000px 為例,下面這段會把 1000px 平均切成三份,每欄約 333px:
.wrapper {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
}Code language: CSS (css)
比例可以不一樣。2fr 1fr 1fr 把可用空間切成 4 份,第一欄占 2 份、後兩欄各占 1 份:
.wrapper {
display: grid;
grid-template-columns: 2fr 1fr 1fr;
}Code language: CSS (css)
容器同樣是 1000px 時,第一欄寬 500px,第二、三欄各 250px。視窗放大或縮小,三欄的比例維持 2:1:1,不需要重算百分比。

固定欄寬與間距先扣除,剩下的空間才分給 fr
fr 可以和 px 這類固定尺寸混著寫,計算時固定寬度與間距會先扣掉,剩下的空間才依 fr 比例切分。下面是第一欄固定 200px,後兩欄分 1 份與 2 份,格子之間留 16px 間距(gap 的寫法在後面專門講,這裡只需要知道它是格子之間的空隙):
.wrapper {
display: grid;
grid-template-columns: 200px 1fr 2fr;
gap: 16px;
}Code language: CSS (css)
容器寬 1000px 時,先扣掉第一欄的 200px,再扣掉兩個間距共 32px,剩下 768px 切成 3 份,1fr 是 256px、2fr 是 512px。換句話說,間距和固定欄寬不會被 fr 攤薄,各欄的最終寬度加上間距,剛好填滿容器。
1fr 被長內容撐開時,改用 minmax(0, 1fr) 維持等寬
fr 最常見的陷阱出在長內容上。1fr 其實等於 minmax(auto, 1fr),最小值是 auto,也就是欄裡內容的最小尺寸。只要某一格放了一長串不能斷行的字,像網址、英文長字或固定寬度的圖片,那一欄就會被撐開,三欄也就不再等寬。
要避開這個狀況,把下限改成 0,寫成 minmax(0, 1fr),欄寬就只看比例、不看內容。下面這份 HTML 上下放了兩個 grid,裡面是同一串長英文字,上方的 .left 用 1fr,下方的 .right 用 minmax(0, 1fr):
<!DOCTYPE html>
<html lang="zh-Hant">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>fr 與 minmax(0, 1fr)</title>
<style>
body {
font-family: sans-serif;
margin: 24px;
}
.wrapper {
display: grid;
gap: 8px;
width: 600px;
margin-bottom: 24px;
}
.left {
grid-template-columns: 1fr 1fr 1fr;
}
.right {
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1fr);
}
.item {
background: #4f7cff;
color: #fff;
padding: 16px;
border-radius: 8px;
}
.right .item {
overflow-wrap: anywhere;
}
</style>
</head>
<body>
<div class="wrapper left">
<div class="item">短內容</div>
<div class="item">AVeryLongEnglishWordThatCannotBeBrokenIntoTwoLines</div>
<div class="item">短內容</div>
</div>
<div class="wrapper right">
<div class="item">短內容</div>
<div class="item">AVeryLongEnglishWordThatCannotBeBrokenIntoTwoLines</div>
<div class="item">短內容</div>
</div>
</body>
</html>Code language: HTML, XML (xml)
瀏覽器會渲染成上下兩組。上方用 1fr 的那組,中間那格的長字串無法斷行,中間欄被撐得特別寬,兩側只剩被擠壓後的窄欄,三欄明顯不等寬;下方用 minmax(0, 1fr) 的那組,三欄各占 600px 扣掉間距後的三分之一,維持等寬。

欄寬不再被撐開之後,長字串會直接超出方格,需要另外處理。範例裡 .right 的項目加了 overflow-wrap: anywhere,允許長字串在任何位置斷行,文字就會留在格子內換行。MDN 說明,anywhere 產生的斷字機會會被算進最小內容尺寸,所以只用這個屬性,也能讓 1fr 不被長字串撐開;兩種寫法擇一,或搭配使用都可以。
設定欄寬與列高常用的 repeat() 和 minmax()
學會 fr 之後,欄一多就開始重複打字,也會遇到「欄寬至少要多少」的需求。repeat() 負責把重複的寫法收成一次,minmax() 負責替軌道設定下限與上限,而後面做 RWD 卡片牆的那一行規則,正是這兩個函式的組合。
repeat() 把重複的欄寬寫成一次
repeat() 的第一個參數是重複次數,第二個參數是要重複的軌道清單。因為重複的是一整份清單,所以不只能重複單一尺寸,也能重複一組不同寬度的樣式,欄數則是次數乘以每組的軌道數。
| 寫法 | 展開後 | 欄數 |
|---|---|---|
repeat(3, 1fr) | 1fr 1fr 1fr | 3 |
20px repeat(6, 1fr) 20px | 20px、6 個 1fr、20px | 8 |
repeat(5, 1fr 2fr) | 1fr 2fr 重複 5 次 | 10 |
以第二種寫法為例,兩側各留一條 20px 的窄欄,中間 6 欄等寬。下面這份 HTML 放了 8 個項目,兩側窄欄用不同顏色標出:
<!DOCTYPE html>
<html lang="zh-Hant">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>repeat() 範例</title>
<style>
body {
font-family: sans-serif;
margin: 24px;
}
.wrapper {
display: grid;
grid-template-columns: 20px repeat(6, 1fr) 20px;
gap: 8px;
}
.item {
background: #4f7cff;
color: #fff;
padding: 16px 0;
border-radius: 8px;
text-align: center;
}
.item:first-child,
.item:last-child {
background: #ff9f43;
}
</style>
</head>
<body>
<div class="wrapper">
<div class="item">1</div>
<div class="item">2</div>
<div class="item">3</div>
<div class="item">4</div>
<div class="item">5</div>
<div class="item">6</div>
<div class="item">7</div>
<div class="item">8</div>
</div>
</body>
</html>Code language: HTML, XML (xml)
畫面上會看到 8 欄、1 列,最左與最右是兩條橘色的 20px 窄欄,中間 6 欄是藍色,彼此等寬並隨視窗伸縮。如果把 repeat(6, 1fr) 改成 repeat(12, 1fr),就是常見的 12 欄格線系統的雛形。
minmax() 替軌道設定下限與上限
minmax(最小, 最大) 的意思是軌道的尺寸要大於等於最小值、小於等於最大值。日常最實用的場景是列高,grid-auto-rows: minmax(100px, auto) 代表每一列最少 100px,內容更多時再自動長高。
下面這份 HTML 用 3 欄搭配這條規則,並讓第 5 個項目放入多行文字:
<!DOCTYPE html>
<html lang="zh-Hant">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>minmax() 範例</title>
<style>
body {
font-family: sans-serif;
margin: 24px;
}
.wrapper {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-auto-rows: minmax(100px, auto);
gap: 8px;
}
.item {
background: #4f7cff;
color: #fff;
padding: 16px;
border-radius: 8px;
}
</style>
</head>
<body>
<div class="wrapper">
<div class="item">1</div>
<div class="item">2</div>
<div class="item">3</div>
<div class="item">4</div>
<div class="item">第 5 格<br>內容比較長<br>第三行<br>第四行<br>第五行</div>
<div class="item">6</div>
</div>
</body>
</html>Code language: HTML, XML (xml)
畫面上第一列的 3 個項目都是 100px 高,第二列因為第 5 格有 5 行字,會自動長高到剛好容得下文字,同一列的第 4、6 格跟著等高。換成固定的 grid-auto-rows: 100px,第 5 格的文字就會溢出方塊,這正是 minmax(100px, auto) 的價值。
minmax() 的最大值也可以是 fr,例如 minmax(120px, 1fr) 代表最少 120px,有多的空間就用 fr 平分,這個寫法就是下一個 RWD 範例的核心。還有一個小規則,如果最大值比最小值還小,最大值會被忽略,整個函式當成最小值處理,所以 minmax(200px, 100px) 的效果等同 200px。
gap 只在軌道之間留出間距
間距用 gap 設定。寫一個值,列與欄的間距都套用;寫兩個值,第一個是列間距、第二個是欄間距,順序是先列後欄。需要單獨調整時,可以分開寫 row-gap 和 column-gap,gap 只是兩者的簡寫。
.wrapper {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 12px 24px;
}Code language: CSS (css)
畫面上是 3 欄 2 列,列與列之間空 12px,欄與欄之間空 24px,因此橫向的空隙比縱向寬。間距只會出現在軌道「之間」,第一條軌道之前、最後一條軌道之後都沒有,這也是 gap 和替每個項目加 margin 最大的差別,容器外圍不會多出一圈空白。
網路上較舊的教學常寫成 grid-gap。這是早期規格使用的名稱,後來更名為 gap,為了相容舊網站,瀏覽器仍然接受 grid-gap 當作別名。新寫的程式碼直接用 gap 就好。
用格線編號指定格子的位置與跨度
自動排列已經夠應付多數情況,但海報式的版面,或是「第一個項目要跨滿整排」的需求,就得手動指定位置。Grid 用的是格線的編號,而不是軌道的編號。grid-column: 1 / 4 的意思是從第 1 條欄線開始,到第 4 條欄線結束,所以 3 欄的版面剛好橫跨全部。
grid-row 的寫法完全對稱。只寫起點、不寫結尾,預設只跨一個軌道;想用跨幾個軌道來表達,可以寫 span,例如 grid-row: span 2 代表往下跨 2 列。負數的線號從尾端往回算,-1 是最後一條線,所以 1 / -1 就是從頭跨到尾。
.wrapper {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-auto-rows: 100px;
gap: 8px;
}
.item:nth-child(1) {
grid-column: 1 / -1;
}
.item:nth-child(2) {
grid-row: span 2;
}Code language: CSS (css)
畫面上第 1 個項目橫跨整整一列(3 欄全占),第 2 個項目停在第二列第一欄,並往下跨 2 列,其餘 4 個項目再依序填進剩下的格子,整體共 3 列。
1 / -1 有一個容易被忽略的限制,它只對明確格線有效。像上面這個範例,列是瀏覽器以隱含格線自動補上的,沒有用 grid-template-rows 寫出來,這時 grid-row: 1 / -1 不會跨滿所有列,只會跨一列。需要「跨滿整個高度」的項目,要先用 grid-template-rows 把列明確定義出來。
grid-area 把 4 條線合寫成一行,順序先列後欄
grid-area 也能一次寫完四條線,順序是列起點、欄起點、列終點、欄終點。grid-area: 1 / 1 / 3 / 2 的意思是從第 1 條列線與第 1 條欄線出發,到第 3 條列線與第 2 條欄線為止,也就是占左上角的第 1 欄、跨 2 列。
/* 四線簡寫 */
.item:nth-child(1) {
grid-area: 1 / 1 / 3 / 2;
}
/* 拆開寫,效果相同 */
.item:nth-child(1) {
grid-row: 1 / 3;
grid-column: 1 / 2;
}Code language: CSS (css)
四個值的順序是「列、欄、列、欄」,與一般習慣的「先欄後列」相反,很容易寫反。日常多半建議用 grid-column 和 grid-row 兩個簡寫,可讀性比四線簡寫好。grid-area 的另一個用途是放自訂名稱,這會在下一個主題派上用場。
grid-template-areas 用名稱畫出整頁版面
grid-template-areas 是 Grid 最直覺的寫法,先替每個區塊命名,再用文字畫出版面圖,改版面時只需要改這張圖。MDN 把這種寫法稱為 ASCII-art 法,所有排版資訊都集中在容器上,項目本身不用再寫任何位置。
下面是常見的頁首、側欄、內容、頁尾版型,header 跨 4 欄,sidebar 占 1 欄,main 占 3 欄,footer 跨 4 欄。
<!DOCTYPE html>
<html lang="zh-Hant">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>grid-template-areas 範例</title>
<style>
body {
font-family: sans-serif;
margin: 24px;
}
.layout {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-auto-rows: minmax(80px, auto);
grid-template-areas:
"header header header header"
"sidebar main main main"
"footer footer footer footer";
gap: 8px;
}
.layout > * {
color: #fff;
padding: 16px;
border-radius: 8px;
}
header {
grid-area: header;
background: #4f7cff;
}
aside {
grid-area: sidebar;
background: #ff9f43;
}
main {
grid-area: main;
background: #2bb673;
}
footer {
grid-area: footer;
background: #6c5ce7;
}
</style>
</head>
<body>
<div class="layout">
<header>頁首 header</header>
<aside>側欄 sidebar</aside>
<main>內容 main</main>
<footer>頁尾 footer</footer>
</div>
</body>
</html>Code language: HTML, XML (xml)
瀏覽器會渲染成三列,最上方的頁首橫跨 4 欄,中間左邊是占 1 欄的側欄、右邊是占 3 欄的內容,最下方的頁尾同樣橫跨 4 欄。區域圖裡每個名稱重複幾次,那個區域就占幾格。想讓頁首只跨 3 欄,把最右邊那個 header 換成 . 留白即可;換掉的若是中間的 header,頁首區域會斷成兩塊、不再是矩形,整條宣告就會失效。

先為每個區塊命名再畫區域圖
這個寫法分成兩步,缺一不可。第一步,在項目上用 grid-area 掛上名稱,例如 header { grid-area: header; };第二步,在容器上用 grid-template-areas 畫出名稱的排列。
很多人只做了其中一步就以為會生效。事實上,grid-area 單獨使用只是命名,本身不產生任何版面;如果拿掉容器上的 grid-template-areas,名稱就找不到對應的區域,在這份範例裡,4 個區塊會疊在同一個格子上。名稱建議挑語意清楚的詞,像 header、sidebar、main、footer,改版時不必翻到 CSS 深處才知道誰是誰。
區域圖的 3 條規則與留白寫法
區域圖看起來像是隨手畫的字串,但它有三條規則,違反任何一條,整個 grid-template-areas 宣告都會被瀏覽器捨棄,畫面直接失效,這是新手最常遇到的狀況。
- 每一列的格數必須相同,整張圖是一個完整的格子表。
- 重複名稱形成的區域必須是矩形,不能是 L 形,也不能中斷。
- 想留空的格子用
.表示,連續的.若彼此之間沒有空白,算一格;有空白隔開才算多格。
違規的典型長相是某一列少寫一格:
.layout {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-template-areas:
"header header header header"
"sidebar main main"
"footer footer footer footer";
}Code language: CSS (css)
第二列只有 3 格,與其他列的 4 格不一致,這條宣告會整個無效,項目不會照區域圖排列,結果和拿掉區域圖一樣,4 個區塊疊在同一格。修正的方式是補齊格數;如果真的想留一格空白,就用 . 補上:
.layout {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-auto-rows: minmax(80px, auto);
grid-template-areas:
"header header header header"
"sidebar main main ."
"footer footer footer footer";
gap: 8px;
}Code language: CSS (css)
套用在上一段的 HTML 後,第二列變成左邊 1 欄是側欄、中間 2 欄是內容、最右邊 1 欄留白,什麼都不放。留白的格子沒有放元素,也就沒有背景色,看起來就是版面裡的一塊空位。區域圖也可以放進媒體查詢裡整張重寫,讓同一份 HTML 在手機與寬螢幕上排出不同的版面。
auto-fit 搭配 minmax() 做出不靠斷點的 RWD
把前面學過的零件拼起來,就是 Grid 最常被拿來做 RWD 的一行,也就是 repeat(auto-fit, minmax(200px, 1fr))。web.dev 的教學指出,用 repeat() 加上 auto-fill 或 auto-fit,可以不指定欄數,讓瀏覽器建立盡可能多的欄軌來填滿容器,再搭配 minmax() 設定最小欄寬,就能做出完全不需要媒體查詢的二維回應式版面。
欄寬的規則在這行裡一次寫完,每一欄最少 200px,容器放得下幾欄就放幾欄,放完之後多出來的空間,平均分給這些欄。
最小欄寬 200px 的卡片牆寫法
下面是一份完整的卡片牆,放了 8 張卡片,CSS 只有一條欄寬規則,沒有任何媒體查詢:
<!DOCTYPE html>
<html lang="zh-Hant">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>auto-fit 卡片牆</title>
<style>
body {
font-family: sans-serif;
margin: 24px;
}
.cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 16px;
}
.card {
background: #4f7cff;
color: #fff;
padding: 24px 16px;
border-radius: 8px;
text-align: center;
}
</style>
</head>
<body>
<div class="cards">
<div class="card">卡片 1</div>
<div class="card">卡片 2</div>
<div class="card">卡片 3</div>
<div class="card">卡片 4</div>
<div class="card">卡片 5</div>
<div class="card">卡片 6</div>
<div class="card">卡片 7</div>
<div class="card">卡片 8</div>
</div>
</body>
</html>Code language: HTML, XML (xml)
視窗寬度改變時,欄數會自己跟著變。視窗約 1100px 時,瀏覽器會排出 4 欄,8 張卡片分成 2 列;縮到約 700px 時變成 3 欄;縮到手機常見的約 380px 時,只剩 1 欄,8 張卡片垂直堆疊。每種寬度下,欄寬都會撐滿整個容器,不會在右側留下一截沒用到的空白;只有卡片數除不盡時,最後一列才會少幾張。

minmax(200px, 1fr) 的上下限很好理解,200px 是欄寬的底線,不夠放下一欄就換行;1fr 是上限,讓剩餘空間平均分給已經排進去的欄。
還有一個小細節,當容器本身比 200px 還窄,例如嵌在窄側欄裡的卡片區,最小值 200px 就會超過容器寬度而溢出。需要保險時可以把最小值寫成 min(200px, 100%),也就是 200px 與容器寬度兩者取較小的那個,完整寫法為 repeat(auto-fit, minmax(min(200px, 100%), 1fr))。
項目較少時,auto-fill 留下空軌道、auto-fit 撐滿整列
auto-fit 還有一個孿生兄弟 auto-fill。項目夠多、塞滿整列時,兩者的畫面完全相同;差別只在項目少於一列能容納的欄數時才看得出來。
MDN 對兩者的定義是,auto-fit 的行為與 auto-fill 相同,只是放完項目後,空的重複軌道會被收合。auto-fill 因此會保留空軌道,現有項目維持原本的寬度;auto-fit 把空軌道收成 0 寬,現有項目的 1fr 就會拉伸去填滿空間。下面用只有 2 張卡片的同一份寫法,上下並排對照:
<!DOCTYPE html>
<html lang="zh-Hant">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>auto-fill 與 auto-fit</title>
<style>
body {
font-family: sans-serif;
margin: 24px;
}
.cards {
display: grid;
gap: 16px;
margin-bottom: 24px;
}
.fill {
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
}
.fit {
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
}
.card {
background: #4f7cff;
color: #fff;
padding: 24px 16px;
border-radius: 8px;
text-align: center;
}
</style>
</head>
<body>
<p>auto-fill</p>
<div class="cards fill">
<div class="card">卡片 1</div>
<div class="card">卡片 2</div>
</div>
<p>auto-fit</p>
<div class="cards fit">
<div class="card">卡片 1</div>
<div class="card">卡片 2</div>
</div>
</body>
</html>Code language: HTML, XML (xml)
視窗約 1100px 時,上方的 auto-fill 會看到 2 張卡片各占一欄寬,右側留下空白的軌道;下方的 auto-fit 則是 2 張卡片各撐到半個容器寬,把整列填滿。

怎麼挑,要看想要的效果。希望卡片維持固定寬度、保留空位,用 auto-fill;希望不管項目多少,卡片都撐滿整列,用 auto-fit。
版面結構大改時用媒體查詢重排區域
auto-fit 管的是欄數,但頁首、側欄、內容、頁尾這種區塊型版面,在手機上要變成單欄堆疊,在寬螢幕又要把側欄放左邊,這時還是需要媒體查詢,在不同斷點重新宣告 grid-template-areas。
MDN 提到,用區域圖排版時,整個版面都寫在 CSS 的同一處,不同斷點的調整也集中在那裡;區域名稱則定義在所有媒體查詢之外,所以不論內容區被排到格線的哪個位置,名稱始終是 main。下面這份範例,窄螢幕是單欄堆疊,視窗寬度達到 48em(約 768px)才改成側欄在左的兩欄:
<!DOCTYPE html>
<html lang="zh-Hant">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>用媒體查詢重排區域</title>
<style>
body {
font-family: sans-serif;
margin: 24px;
}
header {
grid-area: header;
background: #4f7cff;
}
aside {
grid-area: sidebar;
background: #ff9f43;
}
main {
grid-area: main;
background: #2bb673;
}
footer {
grid-area: footer;
background: #6c5ce7;
}
.layout {
display: grid;
gap: 8px;
grid-template-columns: 1fr;
grid-template-areas:
"header"
"main"
"sidebar"
"footer";
}
.layout > * {
color: #fff;
padding: 16px;
border-radius: 8px;
}
@media (width >= 48em) {
.layout {
grid-template-columns: 200px 1fr;
grid-template-areas:
"header header"
"sidebar main"
"footer footer";
}
}
</style>
</head>
<body>
<div class="layout">
<header>頁首 header</header>
<aside>側欄 sidebar</aside>
<main>內容 main</main>
<footer>頁尾 footer</footer>
</div>
</body>
</html>Code language: HTML, XML (xml)
視窗窄於 768px 時,瀏覽器會把四個區塊由上到下排成單欄,順序是頁首、內容、側欄、頁尾,內容排在側欄前面,手機使用者先看到主要資訊。視窗寬度達到 768px 以上時,版面變成兩欄,頁首與頁尾橫跨整列,中間左邊是 200px 寬的側欄,右邊的內容占據剩下的寬度。
這裡的 @media (width >= 48em) 用的是媒體查詢的範圍語法,比傳統的 min-width: 48em 少打幾個字,意思相同。程式碼裡沒有任何一個項目改過 grid-area,改變的只有容器上的那張區域圖,這就是命名區域的好處。
主流瀏覽器都已支援 Grid 與 subgrid
現行主流瀏覽器都已支援 Grid,日常專案不需要為它另寫備援版面。MDN 把 grid-template-columns 標為 Baseline 的 Widely available,自 2017 年 10 月起,各主流瀏覽器都能使用。
Can I use 的資料也呼應這個結論,全球使用率約 97%,Chrome 57、Firefox 52、Safari 10.1、Edge 16 起支援。少數需要留意的例外是 IE 10 與 11,它們只支援早期版本的規格,屬於部分支援;Opera Mini 則完全不支援。
另一項較新的功能是 subgrid,它讓巢狀的格線沿用父格線的軌道,而不是自己再建一組。MDN 目前把它標為 Baseline Widely available,自 2023 年 9 月起各主流瀏覽器都已支援,所以已經可以在正式專案使用。
把 .wrapper 那段規則換來換去,邊改邊看開發人員工具畫出來的格線,比讀任何說明都更快抓到手感。下次排版面之前,不妨先在紙上決定欄要切幾條、每個區塊占幾格,再把數字抄進 grid-template-columns,這就是 grid 最省力的用法。
