TailwindCSS 響應式設計:斷點、mobile-first 與容器查詢全解 | TailwindCSS 完整教學
前面 23 篇我們把單一畫面「排得整齊、看得漂亮、用起來順手」,但真實世界的網頁必須同時面對手機、平板、桌機三種天差地遠的螢幕。這一篇 TailwindCSS 的響應式設計(Responsive Design),要用
sm:、md:、lg:、xl:、2xl:這組斷點(breakpoint)前綴,加上 mobile-first(行動優先)哲學、v4 內建的 max-* 區間變體、任意斷點min-[...],以及依「容器寬度」而非視窗寬度響應的容器查詢(container query)@container,讓同一份程式碼,在所有螢幕上都呈現最佳樣貌。
前言
上一篇 《互動性》 我們把游標、滾動吸附、觸控行為與表單質感一一調校到位,完成了「單一畫面好操作」的最後一哩路。到這裡,讓一個畫面「好看、好讀、好操作」的整套視覺工具你已經收齊。但你可能也發現一個尷尬:我們寫的每個範例,幾乎都預設在某個固定寬度看起來剛好。一旦把它塞進手機的窄螢幕,原本並排的三欄擠成一團、字大到爆版、按鈕小到點不到——這就是響應式設計(Responsive Design) 要解決的核心問題:同一份 HTML,如何在手機、平板、桌機上都自動呈現最合適的排版?
先用一個生活化的類比建立心智模型。如果說前面的視覺工具是在裝潢一間固定大小的房間,那響應式設計就是設計一套「會伸縮的家具」:同一張桌子,在小房間(手機)自動收起兩片桌板變成一人小桌、在大房間(桌機)自動展開變成六人餐桌。你不需要為每種房間各買一套家具(各寫一份頁面),而是設計一套「懂得看空間大小自我調整」的家具。Tailwind 的做法更聰明:它預設「先為最小的房間設計」(mobile-first),再用斷點前綴一層層告訴家具「房間變大時,你可以多展開一點」。
本系列以 TailwindCSS v4 為預設版本。響應式這組能力在 v4 有幾個關鍵升級:容器查詢從外掛變成內建、斷點定義從 JavaScript 設定檔搬進 CSS 的 @theme。本篇你將學到:
- 斷點與 mobile-first:
sm:~2xl:五個斷點的 min-width 語意、行動優先的向上覆蓋機制 - 區間與任意斷點:v4 內建的
max-*變體、min 與 max 疊加做出區間、min-[...]/max-[...]任意斷點 - 容器查詢:v4 內建的
@container+@sm:/@md:、@max-*與@min-[...],讓元件依容器寬度響應 - 自訂斷點:在
@theme中命名專屬斷點,和內建斷點一樣好用
本系列以 TailwindCSS v4 為預設版本,範例皆可直接執行。凡涉及 v3 差異之處,我會特別標註。
核心概念
斷點與容器查詢工具類分類對照
響應式的工具看似繁多,但骨架其實只有兩套「刻度」:一套看視窗寬度(一般斷點),一套看容器寬度(容器查詢)。下表先建立全景:
| 群組 | 前綴 | 觸發條件 | 對應 CSS | 典型用途 |
|---|---|---|---|---|
| 視窗斷點(min) | sm: md: lg: xl: 2xl: | 視窗 ≥ 該寬度 | @media (min-width: …) | 頁面整體布局 |
| 視窗斷點(max,v4) | max-sm: … max-2xl: | 視窗 < 該寬度 | @media (max-width: …) | 「小於某寬」才生效 |
| 任意斷點 | min-[900px]: max-[600px]: | 自訂寬度 | 同上 | 臨時特殊斷點 |
| 容器標記 | @container | (宣告容器) | container-type: inline-size | 開啟容器查詢 |
| 容器查詢(min,v4) | @sm: @md: @lg: … | 容器 ≥ 該寬度 | @container (width >= …) | 可重用元件響應 |
| 容器查詢(max,v4) | @max-md: … | 容器 < 該寬度 | @container (width < …) | 元件「窄容器」樣式 |
| 自訂斷點 | 在 @theme 定義 | 自訂 | @media/@container | 命名專屬斷點 |
視窗斷點的預設刻度是 Tailwind 響應式的地基,務必記熟:
| 斷點前綴 | 最小寬度 | CSS 媒體查詢 | 對應裝置參考 |
|---|---|---|---|
| (無前綴) | 0px | 全部生效 | 手機(預設樣式) |
sm: | 640px | min-width: 640px | 大型手機/直立平板 |
md: | 768px | min-width: 768px | 平板 |
lg: | 1024px | min-width: 1024px | 筆電 |
xl: | 1280px | min-width: 1280px | 桌機 |
2xl: | 1536px | min-width: 1536px | 大型桌機 |
運作原理:mobile-first 的向上覆蓋
理解響應式,只要抓住一個關鍵觀念:Tailwind 的斷點是「min-width(最小寬度)」語意,採 mobile-first(行動優先)向上覆蓋。這句話拆成三點:
- 無前綴的 class 適用於「所有」螢幕,包含最小的手機。所以無前綴的樣式就是你的「手機預設」。
- 有斷點前綴的 class,在達到該寬度後「向上覆蓋」,而非只作用於某一級。
md:text-lg是「≥768px 全部套用text-lg,一路到最大螢幕」,不是「只在 768px~1024px 這一段」。 - 設計流程應從最小螢幕開始,先寫好手機的樣子(無前綴),再用
md:、lg:逐層加上「螢幕變大時的覆寫」。
用一個瀑布圖來看最清楚。下面這行 class="text-sm md:text-base lg:text-lg",三段樣式的生效範圍是層層向右延伸的:
視窗寬度 0px 768px 1024px →
├──────────┼──────────┼────────────────────────
text-sm ██████████████████████████████████████████████ 全部螢幕
md:text-base ██████████████████████████████████ ≥768px 起覆蓋
lg:text-lg ███████████████████████ ≥1024px 起覆蓋
手機(0767px)只吃到 1023px)被 text-sm;平板(768md:text-base 覆蓋;桌機(≥1024px)再被 lg:text-lg 覆蓋。「後面較大的斷點,蓋掉前面較小的設定」——這就是 mobile-first 的全部精髓。理解了它,你就會明白為什麼響應式該「由小寫到大」:先把手機顧好,大螢幕只是漸進地「加碼」。
這裡有個很多人一開始會困惑的地方:為什麼 Tailwind 不做「桌面優先」(desktop-first)? 答案是實務經驗的累積。手機螢幕最窄、限制最多,先把最小空間的排版設計對,往上放大時「內容變多、空間變大」永遠比「內容擠不下、往下砍」容易得多;而且行動裝置流量早已是多數網站的主流,mobile-first 等於「先照顧最大宗的使用者」。你也可以反過來驗證:如果一開始就用一堆 max-* 前綴把桌機當預設、再往小螢幕砍,你會發現斷點之間的覆蓋關係越補越亂——這正是後面「常見錯誤」要談的坑。所以請把「無前綴 = 手機」內化成肌肉記憶,它會替你省下大量的除錯時間。
關鍵術語:區間、容器查詢與自訂斷點
除了基本的 min-width 斷點,v4 還有幾組進階能力,先把術語對照清楚:
max-*區間變體(v4 內建):對應max-width,語意是「小於某寬度時生效」。max-md:就是@media (max-width: 767px),常用來寫「只在手機/窄螢幕」的樣式。它與 min 斷點方向相反,兩者疊加即可做出區間:md:max-xl:flex表示「≥768px 且 <1280px」才 flex——這是精確鎖定某個區間的正解,比分開寫兩條規則更準確。- 任意斷點
min-[…]/max-[…]:當內建的五級刻度不夠用,用方括號任意值臨時造一個斷點。min-[900px]:flex、max-[600px]:text-sm,不必事先定義,寫在哪就在哪生效。 - 容器查詢
@container(v4 內建):讓元件依「父容器寬度」而非視窗寬度響應。先在父層加@container標記容器,子元素就能用@sm:、@md:、@lg:等前綴(對應容器寬度),也支援@max-md:(容器小於某寬)與@min-[475px]:(任意容器寬)。v4 這是內建功能,v3 需額外安裝@tailwindcss/container-queries外掛。 - 自訂斷點:在 CSS 的
@theme區塊加--breakpoint-tablet: 900px,即可像內建斷點般寫tablet:grid-cols-3。v4 把斷點定義搬進@theme;v3 是寫在tailwind.config.js的theme.screens。
實作範例
理論看完,我們用三個高頻範例把「響應式導覽、卡片網格、容器查詢元件」串起來。以下都是完整的 HTML + Tailwind,可直接貼進頁面觀察效果(拖曳瀏覽器寬度或用 DevTools 裝置模擬即可看到變化)。
範例一:mobile-first 響應式導覽列
導覽列是最典型的響應式場景:手機用漢堡選單、桌機展開完整選單。關鍵在先寫手機版(無前綴),再用 md: 切換到桌機版:
<!-- 手機:漢堡按鈕顯示、完整選單隱藏;桌機(md 以上)反過來 -->
<header class="bg-white shadow-sm px-4 py-3 md:px-8">
<div class="mx-auto flex max-w-7xl items-center justify-between">
<a href="/" class="text-xl font-bold text-gray-900">Logo</a>
<!-- 漢堡按鈕:預設顯示,md 以上隱藏 -->
<button class="rounded-lg p-2 hover:bg-gray-100 md:hidden">
<svg class="size-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"/>
</svg>
</button>
<!-- 完整選單:預設隱藏,md 以上以 flex 展開 -->
<nav class="hidden items-center gap-6 md:flex">
<a class="text-gray-600 hover:text-gray-900">首頁</a>
<a class="text-gray-600 hover:text-gray-900">產品</a>
<a class="text-gray-600 hover:text-gray-900">關於</a>
<a class="rounded-lg bg-cyan-600 px-4 py-2 text-white hover:bg-cyan-700">聯絡我們</a>
</nav>
</div>
</header>
這個範例把 mobile-first 的「由小寫到大」示範得很完整。漢堡按鈕用 md:hidden——預設(手機)顯示,md:(≥768px)以上才隱藏。完整選單則相反,用 hidden md:flex——預設隱藏、桌機才以 flex 展開。注意這兩個元件是「一顯一隱」的鏡像關係:同一時間只有一個可見,交界點正好在 md(768px)。容器的 padding 也做了響應:px-4 md:px-8,手機留 16px、桌機放寬到 32px,大螢幕呼吸感更好。這種「無前綴顧手機、md: 顧桌機」的兩段式寫法,是響應式導覽最乾淨的骨架。試著把瀏覽器從寬拉窄,你會看到選單在 768px 這個點瞬間切換。
範例二:自動適應的響應式卡片網格
卡片網格是響應式的另一個經典:欄數要隨螢幕變寬而增加。用 grid-cols-* 搭配斷點前綴,一行搞定「手機一欄、平板兩欄、桌機三欄、寬螢幕四欄」:
<!-- 欄數隨螢幕漸增:1 → 2 → 3 → 4;間距也隨之放寬 -->
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 md:gap-6">
<article class="rounded-xl border border-gray-200 bg-white p-4 shadow-sm">
<div class="mb-3 aspect-video rounded-lg bg-cyan-100"></div>
<h3 class="mb-1 font-semibold text-gray-900">卡片標題</h3>
<p class="text-sm text-gray-500">手機一欄、平板兩欄、桌機三欄、寬螢幕四欄</p>
</article>
<article class="rounded-xl border border-gray-200 bg-white p-4 shadow-sm">
<div class="mb-3 aspect-video rounded-lg bg-teal-100"></div>
<h3 class="mb-1 font-semibold text-gray-900">卡片標題</h3>
<p class="text-sm text-gray-500">grid-cols-* 搭配斷點就是欄數自適應</p>
</article>
<article class="rounded-xl border border-gray-200 bg-white p-4 shadow-sm">
<div class="mb-3 aspect-video rounded-lg bg-cyan-100"></div>
<h3 class="mb-1 font-semibold text-gray-900">卡片標題</h3>
<p class="text-sm text-gray-500">無前綴 grid-cols-1 是手機預設</p>
</article>
<article class="rounded-xl border border-gray-200 bg-white p-4 shadow-sm">
<div class="mb-3 aspect-video rounded-lg bg-teal-100"></div>
<h3 class="mb-1 font-semibold text-gray-900">卡片標題</h3>
<p class="text-sm text-gray-500">拖曳視窗寬度就能看到欄數變化</p>
</article>
</div>
這個範例的核心就在容器的那串斷點 class。grid-cols-1(無前綴)是手機預設——一欄堆疊,窄螢幕最好讀。接著 sm:grid-cols-2(≥640px 兩欄)、lg:grid-cols-3(≥1024px 三欄)、xl:grid-cols-4(≥1280px 四欄),欄數隨螢幕變寬層層遞增。這完全是 mobile-first 的體現:你只描述了「螢幕變大時要加幾欄」,手機的預設一欄則不必特別寫斷點。連間距都跟著響應:gap-4 md:gap-6,手機用較小的 16px 間距省空間、桌機放寬到 24px。這種「一個容器管好所有螢幕欄數」的寫法,就是 Tailwind 響應式最爽快的地方——不用寫一行媒體查詢 CSS,全在 class 裡講清楚。
範例三:v4 容器查詢可重用元件
前兩個範例都看「視窗寬度」,但有個場景它們做不到:同一張卡片,放主內容區想橫向、塞進側欄想縱向。因為視窗寬度是同一個,一般斷點無能為力。這時就輪到 v4 內建的容器查詢登場——讓元件看自己所在的容器寬度做決定:
<!-- 父層加 @container 標記容器;子元素改用 @md: 等「容器斷點」 -->
<div class="@container">
<div class="flex flex-col gap-4 rounded-xl border border-gray-200 bg-white p-4
@md:flex-row @md:p-6">
<!-- 圖片:窄容器全寬、寬容器(@md 以上)固定寬 -->
<img class="aspect-video w-full rounded-lg object-cover @md:aspect-square @md:w-48"
src="/img/demo.jpg" alt="示意圖">
<div class="min-w-0 flex-1">
<div class="mb-2 flex items-start justify-between gap-2">
<h3 class="font-semibold text-gray-900 @lg:text-xl">產品名稱</h3>
<span class="rounded-full bg-cyan-100 px-2 py-0.5 text-xs text-cyan-700
whitespace-nowrap @md:text-sm">熱門</span>
</div>
<!-- 窄容器只顯示兩行、寬容器放到三行 -->
<p class="mb-3 text-sm text-gray-600 line-clamp-2 @md:text-base @lg:line-clamp-3">
這張卡片會依「所在容器」的寬度自動調整版面——放主內容區橫向排列、塞進窄側欄則縱向堆疊。
</p>
<div class="flex flex-wrap items-center gap-3 @md:justify-between">
<span class="text-lg font-bold text-gray-900 @lg:text-2xl">NT$1,299</span>
<button class="rounded-lg bg-cyan-600 px-4 py-2 text-sm text-white
hover:bg-cyan-700 @lg:text-base">加入購物車</button>
</div>
</div>
</div>
</div>
這個範例是容器查詢的教科書用法。第一步是在父層加 @container,把它標記成「查詢基準容器」;少了這一步,底下的 @md: 全部不會生效。第二步是子元素改用 @ 開頭的容器斷點:@md:flex-row(容器 ≥28rem 時橫向)、@md:w-48(圖片轉固定寬)、@lg:text-xl(容器夠寬時標題放大)、@lg:line-clamp-3(寬容器多顯示一行描述)。注意這裡的 @md/@lg 對應的是容器寬度(@md 是 28rem),與視窗斷點的 md(768px)刻度不同,別搞混。
它的威力在於複用:把同一段程式碼放進 <main class="w-2/3"> 它會橫向展開、放進 <aside class="w-64"> 它會自動縮成縱向——完全不用改一個 class。這是一般斷點做不到的,因為兩處的視窗寬度相同、只有容器寬度不同。選擇原則:頁面整體布局用一般斷點(md:),可重用元件用容器查詢(@md:)。 再次提醒,容器查詢在 v4 是內建的,直接寫即可;v3 需安裝 @tailwindcss/container-queries 外掛。
若需要自訂容器斷點或一次性的容器寬度,也能用任意值:@min-[475px]:flex-row 表示「容器 ≥475px 時橫向」、@max-md:flex-col 表示「容器 <28rem 時縱向」,語法與視窗斷點對稱。當頁面上有多個巢狀容器、你想精確指定「回應哪一層容器」時,還能用具名容器:父層寫 @container/card、子元素寫 @md/card:flex-row,用 /card 後綴綁定,避免內外層容器互相干擾。這在複雜的儀表板、巢狀卡片場景特別實用。
常見錯誤與最佳實踐
四個最容易踩的響應式坑
坑一:把斷點當「只在某一級」用,結果影響到更大的螢幕。 想「只在平板隱藏」而寫 md:hidden,結果桌機也一起隱藏。原因:Tailwind 斷點是 min-width、mobile-first 向上覆蓋,md: 是「≥768px 全部生效」,不是「只在 md 一級」。根治法:要做區間,疊加 min 與 max-*——md:max-lg:hidden 才是「只在 md 這一級(768~1023px)隱藏」。
<!-- ❌ 想「只在平板隱藏」卻連桌機都隱藏 -->
<div class="md:hidden">桌機也看不到我</div>
<!-- ✅ 用 min + max-* 疊加,精確鎖定 md 這一級 -->
<div class="md:max-lg:hidden">只在 768~1023px 隱藏</div>
坑二:設計流程「由大寫到小」,斷點層層打架。 先寫桌機版再想辦法覆寫回手機,結果 lg:flex-row max-lg:flex-col 越補越亂。原因:違反 mobile-first,把「大螢幕」當預設、再用一堆 max-* 往回蓋,特異性容易衝突。根治法:一律「由小寫到大」——無前綴寫手機、md:/lg: 漸進加碼。
<!-- ❌ 由大到小:桌機當預設、再用 max-* 蓋回手機,容易打架 -->
<div class="flex-row max-md:flex-col">思路顛倒</div>
<!-- ✅ mobile-first:手機預設縱向、md 以上才橫向 -->
<div class="flex flex-col md:flex-row">先顧手機再加碼</div>
坑三:寫了容器斷點 @md:,卻忘了在父層加 @container。 卡片一堆 @md: class 全部沒反應。原因:容器查詢是「標記容器 + 子元素查詢」雙層機制,少了 @container 就沒有查詢基準。根治法:務必在父層加 @container 才能用 @md: 等前綴。
<!-- ❌ 子元素用了 @md: 卻沒有 @container 父層,完全不生效 -->
<div class="flex flex-col @md:flex-row">不會變橫向</div>
<!-- ✅ 父層先標記 @container -->
<div class="@container">
<div class="flex flex-col @md:flex-row">容器夠寬就橫向</div>
</div>
坑四:每一級斷點都重複宣告同一個值,class 又臭又長。 寫成 text-base md:text-base lg:text-base 這種無意義的重複。原因:mobile-first 下,樣式會一路向上繼承,值不變就不必再宣告。根治法:只在「要改變」的斷點寫,不變的省略。
<!-- ❌ 值沒變還每級重複,徒增噪音 -->
<p class="text-sm sm:text-sm md:text-base lg:text-base xl:text-base">冗餘</p>
<!-- ✅ 只在真正變化的斷點宣告 -->
<p class="text-sm md:text-base">簡潔且等效</p>
響應式設計最佳實踐清單
- 一律 mobile-first,由小寫到大:無前綴顧手機、
md:/lg:漸進覆寫,別用max-*從大往回蓋。 - 值不變就別重複宣告:斷點樣式會向上繼承,
text-sm md:text-base已足夠,不必每級都寫。 - 做區間用 min 疊 max-:
md:max-xl:flex精確鎖定「≥768px 且 <1280px」,比兩條獨立規則準確(此為 v4 內建 max- 變體,v3 需設定)。 - 頁面布局用視窗斷點、可重用元件用容器查詢:整頁欄數用
md:,卡片/widget 用@container+@md:,元件才能真正複用。 - 容器查詢務必雙層齊全:父層
@container+ 子元素@md:,缺一不生效(v4 內建,v3 需裝@tailwindcss/container-queries外掛)。 - 特殊寬度用任意值、常用斷點進 @theme:一次性用
min-[900px]:,會重複用就在@theme加--breakpoint-tablet: 900px命名(v3 寫在tailwind.config.js的theme.screens)。 - 善用 DevTools 裝置模擬驗證:每個斷點交界(640/768/1024/1280/1536)都拖一下確認切換點正確,別只看單一寬度。
小結
這是 TailwindCSS 完整教學 系列的第二十四篇,也是全新章節 TW-5「響應式與狀態」 的開篇。上一篇 《互動性》 讓我們把單一畫面打磨到「好操作」;而這一篇,我們把視野從「打磨一個畫面」正式擴大到「一份程式碼適配所有螢幕」。回顧幾個重點:
- mobile-first 是地基:斷點是 min-width 語意、向上覆蓋——無前綴顧手機,
sm:~2xl:逐層加碼,「後面較大的斷點蓋掉前面的」。 - 五級斷點刻度:
sm:640、md:768、lg:1024、xl:1280、2xl:1536,務必記熟。 - v4 的區間與任意斷點:
max-*內建 max-width 變體、min 疊 max-* 做交集區間、min-[…]/max-[…]任意斷點。 - v4 內建容器查詢:
@container標記容器 +@sm:/@md:依容器寬度響應,讓元件真正可複用(v3 需外掛)。 - 自訂斷點進 @theme:v4 把斷點定義搬進 CSS 的
@theme,和其他設計 token 統一管理。
掌握了斷點,你已經能讓版面「隨螢幕伸縮」。但真實介面還有另一半挑戰:同一個按鈕,滑鼠移上去要變色、按下去要凹陷、focus 時要有外框、禁用時要變灰——這些「隨互動狀態改變樣式」的需求,正是下一篇的主題。下一篇 《狀態變體》 會帶你認識 hover:、focus:、active:、disabled: 這組狀態前綴,以及 group-*、peer-* 這類「連動父子/兄弟元素」的進階變體,把響應式從「看螢幕大小」延伸到「看使用者動作」,讓介面真正活起來。