TailwindCSS 響應式設計:斷點、mobile-first 與容器查詢全解 | TailwindCSS 完整教學

2026/08/24
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:640pxmin-width: 640px大型手機/直立平板
md:768pxmin-width: 768px平板
lg:1024pxmin-width: 1024px筆電
xl:1280pxmin-width: 1280px桌機
2xl:1536pxmin-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)只吃到 text-sm;平板(7681023px)被 md: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]:flexmax-[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-3v4 把斷點定義搬進 @theme;v3 是寫在 tailwind.config.jstheme.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.jstheme.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-* 這類「連動父子/兄弟元素」的進階變體,把響應式從「看螢幕大小」延伸到「看使用者動作」,讓介面真正活起來。

BenZ Software Developer

熱愛技術的軟體開發者,在這裡分享程式開發經驗與學習筆記。

本週主打

AI 自動化入門包

你每天手動在做的那些煩事,其實 AI 可以自己跑。這份給你 10 個照著做就會的自動化工作流 + 50 個複製即用的提示詞,不用會寫程式。

看看這個產品 →