導覽模式:navbar、下拉選單與行動 Drawer 全攻略 | TailwindCSS 完整教學
導覽(Navigation) 是使用者在網站裡移動的動線——navbar、下拉選單、麵包屑、Drawer,每一個都在回答「我現在在哪、可以去哪」。這一篇用 TailwindCSS 把桌機水平
navbar、響應式hamburger選單、group/peer/details下拉、行動版 Drawer 側邊抽屜,以及sticky固定導覽、麵包屑與pagination、tabs一次做齊,搭配 v4 語法與少量 JS,兼顧美觀、響應式與無障礙。
前言
導覽(Navigation) 是一個網站的骨架動線。如果說表單是「使用者對網站說話」,那導覽就是「網站告訴使用者可以去哪裡」——它決定了整站的資訊架構如何被觸及,也直接影響使用者能不能在幾秒內找到目標。一個好的導覽,在桌機是一條清爽的水平列,在手機收成一個 hamburger 按鈕;點下去,選單或側滑或展開,始終讓人知道自己身在何處。
先用一個生活化的類比。導覽系統就像一棟百貨公司的樓層指引:大廳入口那排醒目的樓層看板是navbar(一眼看到所有主要分類);走到某個專櫃時牆上「1F > 女裝 > 洋裝」的指示牌是麵包屑(告訴你目前位置的層級);手扶梯旁那面可以拉開的區域導覽圖是下拉選單;而導覽圖太大、平常收在牆邊、需要時才拉出來的那種,就是行動版的 Drawer。同一份「去哪裡」的資訊,在不同情境用不同形式呈現——這正是導覽模式的核心:內容不變,呈現隨螢幕與情境變形。
本系列以 TailwindCSS v4 為預設版本。本篇你將學到:
- 響應式 navbar:桌機水平導覽 + 手機
hamburger切換,hidden md:flex的斷點切換心法 - 下拉與展開選單:用
group(hover)、peer(點擊)、<details>(純 CSS)三種做法各自的適用場景 - 行動版 Drawer:
fixed+translate-x滑入、遮罩層、Escape 與鎖捲動的 JS 收尾 - 輔助導覽:
sticky固定列的遮擋問題、麵包屑、pagination分頁與tabs分頁籤
本系列以 TailwindCSS v4 為預設版本,範例皆以 v4 語法示範。凡涉及 v3 差異之處,我會特別標註。
核心概念
導覽模式分類:四種形態各司其職
導覽元件看似五花八門,但可以歸成四大類,理解分類能幫你在對的場景選對工具:
| 類別 | 代表元件 | 典型用途 | 主要技術 |
|---|---|---|---|
| 頁面框架導覽 | Navbar、Sidebar、Bottom Nav | 承載整站主要連結 | flex、sticky、hidden md:flex 斷點切換 |
| 內容切換導覽 | Tabs、Dropdown、Accordion | 同層內容或子選單切換 | group、peer、<details> |
| 位置輔助導覽 | Breadcrumb、Pagination、Step | 告知位置、翻頁、流程進度 | flex、before:content、aria-current |
| 行動導覽 | Hamburger、Drawer | 小螢幕收納主導覽 | translate-x、fixed、遮罩 + 少量 JS |
這張表值得記的一條線是:同一份導覽資訊,在桌機用「框架導覽」攤開、在手機用「行動導覽」收起。你不會為手機和桌機各寫一套完全不同的連結,而是用 Tailwind 的響應式前綴(md:、lg:)控制同一組連結在不同螢幕的呈現形態——桌機 hidden md:flex 顯示水平列,手機顯示 hamburger 觸發的 Drawer。
框架導覽本身也有幾種形態,選哪種取決於應用型態。內容型網站(部落格、行銷頁、電商)幾乎都用頂部 navbar,因為橫向連結少、視覺輕、給內容留最多空間;後台系統、管理面板則常用側邊欄(sidebar),因為功能項目多、需要分組與巢狀,垂直排列比擠在一條橫列上清楚得多——側邊欄用 w-64 固定寬度、flex flex-col 垂直排列,分組用 <p> 小標加 <ul> 群組即可;而純行動 App 風格的介面則偏好底部導覽(bottom nav),用 fixed bottom-0 貼底、md:hidden 只在手機出現,把最常用的 3–5 個入口放在拇指最好按的位置。這三者不是互斥的,一個複雜產品完全可能桌機用側邊欄、手機用底部導覽、行銷頁用頂部 navbar,重點是依情境挑對形態,而非硬套同一種。
響應式導覽策略與斷點
導覽的響應式核心是「哪些東西在哪個螢幕出現」。Tailwind 用斷點前綴精準控制:
| 螢幕 | 典型模式 | Tailwind 寫法 |
|---|---|---|
| 手機(< 768px) | Hamburger + Drawer,或底部 Bottom Nav | 桌機連結 hidden、按鈕 md:hidden |
| 平板(768px–1024px) | 精簡水平 navbar | hidden md:flex 顯示核心連結 |
| 桌機(> 1024px) | 完整水平 navbar 或側邊欄 | lg: 補上次要連結與 CTA |
關鍵心法是兩組互斥的顯示切換:桌機導覽區塊用 hidden md:flex(手機藏、桌機顯示),hamburger 按鈕用 md:hidden(手機顯示、桌機藏)。這兩者在同一個斷點 md 交接,達成「小螢幕看到按鈕、大螢幕看到完整列」的無縫切換。
要特別提醒的是斷點的選擇要看內容而非死記。md(768px)是最常見的 navbar 切換點,但如果你的桌機導覽連結很多、CTA 又長,768px 可能還塞不下,這時把切換點提到 lg(1024px)更保險;反之連結很少的極簡站台,sm(640px)就能切。判斷準則很單純:在哪個寬度以下,水平列開始擁擠或換行,就在那個斷點收成 hamburger。另外一個實務細節是,行動選單展開的區塊(範例一裡的 #mobile-menu)最好同時掛 md:hidden,避免使用者在手機展開選單後把視窗拉寬到桌機尺寸時,展開選單和桌機水平列同時出現的錯亂畫面。
三種選單觸發機制:group / peer / details
下拉與展開選單是導覽最容易寫錯的地方,先分清楚三種純 CSS(或近乎純 CSS)的機制:
group(hover 觸發):在父容器加group,子元素用group-hover:opacity-100 group-hover:visible依父層 hover 狀態現身。最適合桌機的 hover 下拉選單。缺點是觸控裝置沒有 hover,行動版要另備方案。peer(點擊切換):在前方兄弟元素(通常是隱藏的<input type="checkbox">)加peer,後方選單用peer-checked:block反應。適合「點一下展開、再點收合」的切換,純 CSS 就能記住開關狀態。<details>/<summary>(語意展開):HTML 原生的可展開容器,<summary>是點擊觸發器,展開時自動加上open屬性,可用open:或group-open:變體驅動樣式(如箭頭旋轉)。天生具備鍵盤操作與無障礙語意,是最省力、最穩健的展開選單做法。
關鍵術語:aria-current="page" 標記「目前所在頁面」的連結,讓螢幕閱讀器與視覺樣式都能標示當前位置;aria-expanded 表示可展開元素(如 hamburger 按鈕)目前是開是關;aria-controls 把觸發器和它控制的選單 ID 綁定。這三個屬性是導覽無障礙的最小集合,無論用哪種技術實作都不該省略。
還有一個容易被忽略但很重要的角色是語意地標(landmark)。把主導覽包在 <nav aria-label="主要導覽"> 裡,把麵包屑包在 <nav aria-label="麵包屑"> 裡——<nav> 是 HTML 的地標元素,螢幕閱讀器使用者可以用「跳到下一個地標」快速在頁面區塊間穿梭,而 aria-label 讓同一頁多個 <nav> 能被區分。當一頁同時有主導覽、麵包屑、分頁三個 <nav> 時,各自的 aria-label 就是它們的名字。這件事零視覺成本、卻大幅提升輔助技術使用者的效率,是導覽無障礙裡 CP 值最高的一步。
實作範例
理論看完,動手做三個最有代表性的導覽模式:一個響應式 navbar + hamburger、一組下拉選單(group 與 details 兩種寫法),以及行動版 Drawer 側邊抽屜。前提是你已在 globals.css 引入 Tailwind v4(@import "tailwindcss";)。
範例一:響應式 navbar + hamburger 切換
navbar 的骨架是一條 flex 水平列,用 justify-between 把 Logo、連結、操作按鈕分置;桌機連結用 hidden md:flex 只在中大螢幕出現,hamburger 按鈕用 md:hidden 只在手機出現:
<header class="sticky top-0 z-40 border-b border-gray-200 bg-white/80 backdrop-blur-sm">
<nav aria-label="主要導覽" class="mx-auto flex h-16 max-w-6xl items-center justify-between px-4">
<!-- 左側:Logo -->
<a href="/" class="flex shrink-0 items-center gap-2 font-bold text-gray-900">
<span class="flex size-8 items-center justify-center rounded-lg bg-cyan-600 text-white">B</span>
BenzHub
</a>
<!-- 中間:桌機水平連結(手機隱藏) -->
<div class="hidden items-center gap-1 md:flex">
<a href="#" aria-current="page"
class="rounded-lg bg-cyan-50 px-3 py-2 text-sm font-medium text-cyan-700">首頁</a>
<a href="#"
class="rounded-lg px-3 py-2 text-sm font-medium text-gray-600 transition-colors hover:bg-gray-100 hover:text-gray-900">產品</a>
<a href="#"
class="rounded-lg px-3 py-2 text-sm font-medium text-gray-600 transition-colors hover:bg-gray-100 hover:text-gray-900">定價</a>
</div>
<!-- 右側:桌機 CTA(手機隱藏) -->
<a href="#"
class="hidden rounded-lg bg-cyan-600 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-cyan-700 md:inline-flex">
免費試用
</a>
<!-- 手機 hamburger 按鈕(桌機隱藏) -->
<button type="button"
aria-expanded="false" aria-controls="mobile-menu"
onclick="const m=document.getElementById('mobile-menu');
const open=m.classList.toggle('hidden');
this.setAttribute('aria-expanded', String(!open));"
class="rounded-lg p-2 text-gray-600 transition-colors hover:bg-gray-100 md:hidden">
<span class="sr-only">開啟選單</span>
<svg class="size-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
</svg>
</button>
</nav>
<!-- 手機展開選單(桌機永遠隱藏) -->
<div id="mobile-menu" class="hidden border-t border-gray-200 px-4 py-3 md:hidden">
<a href="#" aria-current="page"
class="block rounded-lg bg-cyan-50 px-3 py-2 text-sm font-medium text-cyan-700">首頁</a>
<a href="#" class="block rounded-lg px-3 py-2 text-sm text-gray-700 hover:bg-gray-100">產品</a>
<a href="#" class="block rounded-lg px-3 py-2 text-sm text-gray-700 hover:bg-gray-100">定價</a>
</div>
</header>
幾個關鍵細節值得拆解。斷點互斥切換是這段的核心:桌機連結區塊 hidden md:flex(小螢幕藏、md 以上以 flex 顯示),hamburger 按鈕 md:hidden(小螢幕顯示、md 以上藏),兩者剛好在 md 斷點無縫交接。hamburger 按鈕同步 aria-expanded:點擊時不只切換選單的 hidden,還把 aria-expanded 從 false 改成當前狀態,螢幕閱讀器才知道選單是開是關——這一步最常被漏掉。navbar 用 sticky top-0 z-40 讓它捲動時固定在頂端,z-40 確保它蓋在內容之上,bg-white/80 backdrop-blur-sm 則做出半透明毛玻璃效果。
範例二:下拉選單(group hover 與 details 兩種寫法)
先看桌機 hover 下拉,用 group:在父容器加 group,面板預設 invisible opacity-0,父層 hover 時用 group-hover: 現身,再加 group-focus-within: 讓鍵盤 Tab 進去也能展開:
<div class="group relative inline-block">
<button type="button"
class="flex items-center gap-1 rounded-lg px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-100">
產品
<svg class="size-4 text-gray-400 transition-transform group-hover:rotate-180"
fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
</svg>
</button>
<!-- 下拉面板:hover 或內部聚焦時現身 -->
<div class="invisible absolute left-0 top-full z-50 mt-1 w-56 rounded-xl border border-gray-200 bg-white p-1 opacity-0 shadow-lg transition-all
group-hover:visible group-hover:opacity-100
group-focus-within:visible group-focus-within:opacity-100">
<a href="#" class="block rounded-lg px-3 py-2 text-sm text-gray-700 hover:bg-gray-100">產品總覽</a>
<a href="#" class="block rounded-lg px-3 py-2 text-sm text-gray-700 hover:bg-gray-100">功能列表</a>
<a href="#" class="block rounded-lg px-3 py-2 text-sm text-gray-700 hover:bg-gray-100">價格方案</a>
</div>
</div>
這裡用 invisible opacity-0 而非 hidden 來隱藏面板,是為了讓 transition-all 的淡入動畫有作用(hidden 無法過渡)。group-focus-within: 這一組是別忘的無障礙補強:純用 group-hover: 的話,鍵盤使用者 Tab 進面板時它是隱形的,加上 group-focus-within: 才能讓鍵盤操作也展開。
但 hover 下拉在觸控裝置上沒有 hover,行動版該怎麼辦?最穩健的答案是改用 <details>,它天生支援點擊觸發與鍵盤操作,不挑裝置:
<details class="group relative">
<summary
class="flex cursor-pointer list-none items-center gap-1 rounded-lg px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-100">
產品
<!-- 展開時箭頭旋轉:用 group-open: 偵測 details 的 open 狀態 -->
<svg class="size-4 text-gray-400 transition-transform group-open:rotate-180"
fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
</svg>
</summary>
<div class="mt-1 w-56 rounded-xl border border-gray-200 bg-white p-1 shadow-lg">
<a href="#" class="block rounded-lg px-3 py-2 text-sm text-gray-700 hover:bg-gray-100">產品總覽</a>
<a href="#" class="block rounded-lg px-3 py-2 text-sm text-gray-700 hover:bg-gray-100">功能列表</a>
</div>
</details>
<details> 版的巧妙有三:<summary> 加 list-none(搭配 cursor-pointer)收掉瀏覽器預設的三角箭頭,改用自訂 SVG;group-open:rotate-180 讓箭頭在展開時旋轉,group-open: 對應 <details> 展開後自動加上的 open 屬性;整段零 JavaScript,點擊展開、再點收合、Enter/Space 鍵操作全部原生支援。這也呼應了上一篇《表單模式》的原則——能交給原生與純 CSS 的,就別自己刻。
範例三:行動版 Drawer 側邊抽屜
Drawer 是需求最完整的行動導覽:它從畫面外滑入、蓋一層遮罩、點遮罩或按 Escape 關閉、開啟時鎖住背景捲動。這些行為超出純 CSS 能力,得用少量 JavaScript 收尾。核心 Tailwind 技巧是 fixed inset-y-0 left-0 貼左全高 + -translate-x-full(初始推到畫面外)/ translate-x-0(滑入歸位)的位移切換:
<!-- 觸發按鈕 -->
<button type="button" onclick="openDrawer()"
aria-controls="drawer" aria-expanded="false"
class="rounded-lg p-2 text-gray-600 hover:bg-gray-100 md:hidden">
<span class="sr-only">開啟選單</span>
<svg class="size-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
</svg>
</button>
<!-- 遮罩層:z-index 要比 Drawer 低、比 navbar 高 -->
<div id="drawer-overlay" onclick="closeDrawer()"
class="fixed inset-0 z-40 hidden bg-black/50 transition-opacity"></div>
<!-- Drawer 主體 -->
<aside id="drawer" aria-label="行動導覽選單"
class="fixed inset-y-0 left-0 z-50 flex w-72 -translate-x-full flex-col bg-white shadow-xl transition-transform duration-300 ease-in-out">
<div class="flex h-16 shrink-0 items-center justify-between border-b border-gray-200 px-4">
<span class="font-bold text-gray-900">BenzHub</span>
<button type="button" onclick="closeDrawer()"
class="rounded-lg p-2 text-gray-500 hover:bg-gray-100">
<span class="sr-only">關閉選單</span>
<svg class="size-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
<nav class="flex-1 space-y-1 overflow-y-auto p-3">
<a href="#" aria-current="page"
class="block rounded-lg bg-cyan-50 px-3 py-2.5 text-sm font-medium text-cyan-700">首頁</a>
<a href="#" class="block rounded-lg px-3 py-2.5 text-sm text-gray-700 hover:bg-gray-100">產品</a>
<a href="#" class="block rounded-lg px-3 py-2.5 text-sm text-gray-700 hover:bg-gray-100">定價</a>
</nav>
</aside>
<script>
const drawer = document.getElementById('drawer');
const overlay = document.getElementById('drawer-overlay');
function openDrawer() {
drawer.classList.replace('-translate-x-full', 'translate-x-0'); // 滑入
overlay.classList.remove('hidden'); // 顯示遮罩
document.body.classList.add('overflow-hidden'); // 鎖背景捲動
}
function closeDrawer() {
drawer.classList.replace('translate-x-0', '-translate-x-full'); // 滑出
overlay.classList.add('hidden');
document.body.classList.remove('overflow-hidden');
}
// 按 Escape 關閉
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') closeDrawer();
});
</script>
拆解 Drawer 的四個關鍵。位移動畫是靈魂:-translate-x-full 讓 Drawer 初始躲在畫面左外側,transition-transform duration-300 加上 JS 切換到 translate-x-0 就得到平滑滑入;比起切換 hidden,位移動畫的體驗好得多。z-index 的三層堆疊要記牢:內容最底、遮罩 z-40、Drawer z-50——遮罩必須蓋住內容但露出 Drawer。鎖背景捲動用 document.body 加 overflow-hidden,避免 Drawer 開著時背景還能滑動的尷尬。Escape 關閉是鍵盤無障礙的基本盤,keydown 監聽 Escape 一行就補上。
輔助導覽:sticky、麵包屑、pagination 與 tabs
主導覽之外,幾個輔助元件也常用。先看麵包屑,用 <ol> 加 aria-label 語意化,分隔符用 before:content 純 CSS 產生,不佔語意:
<nav aria-label="麵包屑">
<ol class="flex items-center text-sm">
<li><a href="/" class="text-gray-500 hover:text-gray-900">首頁</a></li>
<li class="before:mx-2 before:text-gray-300 before:content-['/']">
<a href="/tailwindcss" class="text-gray-500 hover:text-gray-900">TailwindCSS</a>
</li>
<li class="before:mx-2 before:text-gray-300 before:content-['/']">
<span aria-current="page" class="font-medium text-gray-900">導覽模式</span>
</li>
</ol>
</nav>
pagination 分頁用 flex 排一列頁碼,當前頁用 aria-current="page" 標記並填色:
<nav aria-label="分頁導覽">
<ul class="flex items-center gap-1">
<li><a href="#" class="rounded-lg border border-gray-300 px-3 py-2 text-sm text-gray-600 hover:bg-gray-50">上一頁</a></li>
<li><a href="#" class="flex size-9 items-center justify-center rounded-lg border border-gray-300 text-sm text-gray-600 hover:bg-gray-50">1</a></li>
<li><a href="#" aria-current="page" class="flex size-9 items-center justify-center rounded-lg bg-cyan-600 text-sm font-medium text-white">2</a></li>
<li><a href="#" class="flex size-9 items-center justify-center rounded-lg border border-gray-300 text-sm text-gray-600 hover:bg-gray-50">3</a></li>
<li><a href="#" class="rounded-lg border border-gray-300 px-3 py-2 text-sm text-gray-600 hover:bg-gray-50">下一頁</a></li>
</ul>
</nav>
tabs 分頁籤做同層內容切換,底線式最常見:當前頁籤用 border-b-2 border-cyan-600,面板用 role="tabpanel" 對應(切換邏輯需少量 JS,此處聚焦樣式):
<div class="border-b border-gray-200">
<div class="-mb-px flex gap-4" role="tablist" aria-label="內容分類">
<button role="tab" aria-selected="true"
class="border-b-2 border-cyan-600 px-1 py-3 text-sm font-medium text-cyan-600">概覽</button>
<button role="tab" aria-selected="false"
class="border-b-2 border-transparent px-1 py-3 text-sm font-medium text-gray-500 hover:border-gray-300 hover:text-gray-700">功能</button>
<button role="tab" aria-selected="false"
class="border-b-2 border-transparent px-1 py-3 text-sm font-medium text-gray-500 hover:border-gray-300 hover:text-gray-700">定價</button>
</div>
</div>
底線式 tabs 的小技巧是外層容器 border-b(整條底線)搭配內層 -mb-px(把頁籤往下推一像素),讓當前頁籤的 border-b-2 剛好蓋在容器底線上,對齊得乾淨俐落。
順帶一提膠囊式 tabs(pill tabs) 是另一種常見變體,把整組頁籤放進一個 bg-gray-100 rounded-xl p-1 的容器,當前頁籤用 bg-white shadow-sm rounded-lg 做出「浮起」感,適合放在卡片內或工具列。兩種 tabs 的取捨很簡單:底線式更輕、適合大區塊內容切換(像文章的概覽/功能/定價),膠囊式更聚焦、適合小範圍的篩選切換(像「全部/進行中/已完成」)。而 tabs 與下拉選單的差別也值得釐清——tabs 是「同層平行內容」的切換,選單是「跳往不同頁面」的導覽,兩者語意不同,別把該用 tabs 的地方做成一堆連結。
常見錯誤與最佳實踐
坑一:hamburger 選單只切 hidden,不同步 aria-expanded
最常見的無障礙疏漏,就是點 hamburger 只做 classList.toggle('hidden'),卻沒更新按鈕的 aria-expanded。這樣視覺上選單開了,但螢幕閱讀器仍以為它是關的,鍵盤使用者無從得知狀態:
<!-- ❌ 只切選單顯示,按鈕狀態沒同步 -->
<button aria-expanded="false"
onclick="menu.classList.toggle('hidden')">選單</button>
正確做法:切換顯示的同時同步 aria-expanded,並用 aria-controls 綁定選單 ID(範例一即如此)。純 CSS 版若改用 <details>,則能天然免除這個問題——<details> 的展開狀態瀏覽器會自動反映給輔助技術。
坑二:sticky/fixed 導覽遮住錨點內容
sticky top-0 的 navbar 很好用,但點錨點連結跳到某段落時,段落頂端會被固定的 navbar 蓋住,讀者看到的是被切掉一截的標題:
<!-- ❌ 點 #section 跳過去,標題被 h-16 的 navbar 遮住 -->
<h2 id="section">章節標題</h2>
正確做法:給錨點目標加 scroll-mt-*(對應 CSS scroll-margin-top),預留 navbar 的高度。navbar 是 h-16,目標就加 scroll-mt-16,甚至可以全域套用:
<!-- ✅ 捲動定位時預留 navbar 高度,標題不被遮 -->
<h2 id="section" class="scroll-mt-16">章節標題</h2>
另外別忘了 sticky 的兩個前提:它需要一個可捲動的父容器,且父容器不能有 overflow-hidden,否則 sticky 會悄悄失效——這是很多人 debug 半天找不到原因的隱形坑。
坑三:hover 下拉在手機上完全無法使用
用 group-hover: 做的桌機下拉在觸控裝置上沒有 hover 事件,手機使用者點下去毫無反應,等於選單消失:
<!-- ❌ 只靠 hover:觸控裝置點不開 -->
<div class="group">
<button>產品</button>
<div class="invisible group-hover:visible">...選單...</div>
</div>
正確做法:hover 下拉限用於桌機(hidden md:block),行動版改用 <details> 點擊展開或收進 Drawer;或直接全站統一用 <details>,一套邏輯桌機手機通吃。原則是任何導覽都必須在觸控裝置上可操作,hover 只能是桌機的增強,不能是唯一入口。
坑四:移除 outline 卻不補聚焦樣式
導覽全是可聚焦的連結與按鈕,為了美觀寫 focus:outline-none 卻不補替代樣式,鍵盤使用者 Tab 過導覽時會完全看不出焦點在哪:
<!-- ❌ 拿掉 outline 又不補,鍵盤導覽迷路 -->
<a href="#" class="focus:outline-none">連結</a>
正確做法:移除預設 outline 的同時,用 focus-visible: 補一個清晰的聚焦環,只在鍵盤聚焦時顯示,不干擾滑鼠點擊:
<!-- ✅ 補上 focus-visible 聚焦環 -->
<a href="#"
class="rounded-lg focus:outline-none focus-visible:ring-2 focus-visible:ring-cyan-500 focus-visible:ring-offset-2">連結</a>
最佳實踐小結
- 斷點互斥切換:桌機導覽
hidden md:flex、hamburgermd:hidden,在同一斷點無縫交接。 - 選單技術選型:hover 下拉用
group(限桌機)、點擊切換用peer、語意展開用<details>(最穩健)。 - Drawer 用少量 JS:
translate-x滑入 + 遮罩 + Escape + 鎖捲動,無障礙屬性同步不可省。 - sticky 防遮擋:錨點目標加
scroll-mt-16,父容器別用overflow-hidden。 - 無障礙最小集:
aria-current、aria-expanded、aria-controls,加上聚焦樣式必補。
小結
這是 TailwindCSS 完整教學 系列的第三十五篇。上一篇《表單模式》,我們把最考驗一致性的輸入框、驗證與自訂控制項一路樣式化;這一篇,我們把視線從「單一頁面的輸入」拉高到「整站的移動動線」,看導覽如何讓使用者在頁面間穿梭時始終知道自己在哪、能去哪。回顧幾個重點:
- 四種導覽形態:頁面框架(navbar/sidebar)、內容切換(tabs/dropdown)、位置輔助(breadcrumb/pagination)、行動導覽(hamburger/drawer),同一份資訊隨螢幕變形。
- 響應式切換:
hidden md:flex與md:hidden兩組互斥顯示,是桌機水平列與手機 hamburger 無縫交接的核心。 - 選單三機制:
group(hover)、peer(點擊)、<details>(語意展開)各有適用場景,能純 CSS 就別寫 JS,<details>最穩健。 - Drawer 與輔助導覽:
translate-x滑入的 Drawer 需 JS 補齊遮罩、Escape 與鎖捲動;麵包屑、pagination、tabs 用flex與aria-current就能做得語意完整。 - 無障礙:
aria-current/aria-expanded/aria-controls三件套、sticky 用scroll-mt-*防遮擋、移除outline必補focus-visible:聚焦環。
把導覽做好,使用者才不會在你的網站裡迷路。接下來,我們要把焦點放到「網站如何回應使用者的操作」——下一篇《回饋模式》,將帶你用 TailwindCSS 打造 alert、toast、loading、skeleton 與 modal,看介面如何在使用者每一次點擊後給出清楚、即時又不打擾的回饋。