滑鼠移到「模板分類」,下拉連結就出現;換成鍵盤,卻怎麼也進不去。這種選單看起來正常,訪客仍可能卡在網站入口。檢查時先把滑鼠放旁邊,從網址列按 Tab 進入頁面,試著找到一個分類,再回到正文。
本文處理一般網站的展開式導覽:按一下分類按鈕,顯示幾個頁面連結。它不需要做成桌面軟體那種複雜選單。先保留原生按鈕、連結與清楚的焦點,會比較容易維護。
先走一條完整路徑
挑一個實際任務,例如「從首頁進入 MacCMS 模板分類」。按 Tab,觀察目前焦點在哪裡;到了展開按鈕,按 Enter 或空白鍵。接著繼續 Tab,應能進到剛展開的連結。用 Shift+Tab 反向走,也應能回到前一個控制項。
不要只確認第一個按鈕會亮。第二層連結能否操作、關閉後能否離開、下一個區塊能否接到焦點,才是一條完整路徑。若 Tab 已經跳到正文,下拉選單仍停在畫面上遮住內容,也要記下來。
網站若有登入按鈕、搜尋框或其他導覽,依畫面順序一起走過。Tab 跳動的順序若與左右、上下排列差很多,先查 HTML 排列和 CSS 的視覺重排,不要先用一串正數 tabindex 強行指定順序。
展開用 button,前往頁面用 a
「模板分類」若只負責展開連結,就用 button;「MacCMS 模板」會前往分類頁,則用有 href 的 a。把可點文字放在普通 div 上,再另外補鍵盤事件,很容易漏掉空白鍵、焦點或狀態。
<nav class="site-nav" aria-label="主要導覽">
<button type="button" id="template-toggle"
aria-expanded="false" aria-controls="template-links">
模板分類
</button>
<ul id="template-links" hidden>
<li><a href="/maccms/">MacCMS 模板</a></li>
<li><a href="/wordpress/">WordPress 主題</a></li>
</ul>
</nav>
範例路徑要換成自己網站真正存在的分類網址。展開時移除 hidden,同時把 aria-expanded 改成 true;收起則反向更新。aria-controls 指向被控制區塊的 ID,整頁不能重複使用同一個 ID。
這裡不用 role="menu"。WAI 的網站 disclosure 導覽範例也保留一般導覽連結;menu 角色會帶來另一組鍵盤互動預期。不要為了讓程式碼看起來更完整,加上自己沒有實作的角色。
關閉後,讓訪客知道自己在哪裡
展開按鈕本來就能再次按下收起,但訪客進到子連結後,也應有順手的退出方式。對這類導覽可讓 Esc 收起選單,並把焦點放回原展開按鈕。之後繼續 Tab,就能移到下一個頂層入口。
const toggle = document.querySelector('#template-toggle');
const links = document.querySelector('#template-links');
const nav = toggle.closest('nav');
function closeLinks() {
links.hidden = true;
toggle.setAttribute('aria-expanded', 'false');
}
toggle.addEventListener('click', () => {
links.hidden = !links.hidden;
toggle.setAttribute('aria-expanded', String(!links.hidden));
});
nav.addEventListener('keydown', event => {
if (event.key === 'Escape' && !links.hidden) {
closeLinks();
toggle.focus();
}
});
nav.addEventListener('focusout', event => {
if (!nav.contains(event.relatedTarget)) closeLinks();
});
這段是單一展開區塊的起點。網站有多層分類或多個展開按鈕時,每組控制項要各自配對,並決定開啟第二組是否關閉第一組。不要把焦點鎖在一般網站導覽裡;訪客應能繼續移往正文。

收起來的連結,不能繼續接到焦點
常見問題是只把子選單設成透明,或移出畫面,裡面的連結仍留在 Tab 順序。訪客會看見焦點突然消失,按好幾次後才回到可見位置。用 hidden 收起可同時處理顯示與一般焦點進入,但仍要查主題是否用 CSS 覆寫 hidden 的顯示效果。
展開後不要立刻任意把焦點丟到頁面其他地方。這個做法保留焦點在按鈕,讓下一次 Tab 進入連結;若採另一種互動,需要讓行為一致並重新驗證。滑鼠 hover 若也會展開,鍵盤焦點與 Esc 必須能對應處理,不能只在滑鼠離開時關閉。
看得到焦點,才能判斷走到哪裡
若模板寫了 outline: none,先找出它套用在哪些控制項。不要刪掉瀏覽器的焦點提示卻沒有替代樣式。可以只在鍵盤焦點時加清楚外框:
.site-nav button:focus-visible,
.site-nav a:focus-visible {
outline: 3px solid #5737cb;
outline-offset: 4px;
}
顏色與粗細要放回自己的背景上確認。深色導覽、圖片背景及黏住的頁首,都可能讓外框變得不清楚;容器的 overflow 也可能裁掉外框。停在第一個與最後一個子連結,確認它們沒有被邊界或浮層遮住。
改好後重新整理,分別試收起、展開、Esc 關閉、反向 Tab 及離開導覽。再把滑鼠 hover、觸控與螢幕閱讀器納入自己的驗收;鍵盤能走完是必要的一步,不能單憑它宣稱所有輔助科技都支援。若遇到的是表單按鈕被停用,可另看停用按鈕如何交代原因,那是不同的操作問題。
遇到問題,把卡住的那一步交給維護者
如果你使用的是已安裝的 WordPress 主題或 MacCMS 模板,不必急著把這段 JavaScript 貼進正式站。先把問題縮小到一個動作,例如「Tab 能到模板分類,但 Enter 沒展開」,或「展開後按 Tab 跳過所有子連結」。維護者拿到這種描述,才知道要查按鈕事件、隱藏方式還是 HTML 順序。
也記錄出問題的頁面網址與選單名稱。同一套模板可能在桌機用一個導覽、手機用另一個導覽;桌機修改成功,不能直接推成手機入口也好了。有些模板在窄視窗會把整個導覽收進抽屜,需要另外檢查開啟抽屜、進入分類與離開的完整流程。
先查頁面是否同時存在兩份相同 ID。若桌機導覽與手機導覽都用了 template-toggle,程式可能只找到第一個按鈕,而你實際操作的是第二個。將每一組 ID 分開,讓按鈕的 aria-controls 指向自己的子選單,再確認事件綁定的是正確元素。
看得見連結卻不能 Tab 到它時,檢查是否有 tabindex="-1"、祖先仍被隱藏,或某個事件攔截了 Tab。不要直接把每個連結改成 tabindex 1、2、3;這會影響整頁的焦點順序。普通連結只要有有效 href,通常就已有原生鍵盤入口。
如果按 Esc 後焦點消失,檢視關閉時是否移除了目前聚焦的元素,卻忘記將焦點放回仍存在的展開按鈕。修正後不要只重複一次成功動作:連續開關兩次,再從不同子連結關閉,確認沒有只對第一個連結生效。
最後請一位沒有參與這次修改的人,試著完成普通訪客任務,例如「從導覽找到聯絡方式,然後回正文閱讀」。如果對方需要你口頭提醒每一步,導覽名稱、層級或焦點提示還可以再調整。把舊檔和這次變更留好;發現其他入口被影響時,只回復這次導覽改動,再繼續找原因。

評論0