承昨日談色彩對比這道「看得見」的無障礙門檻,今天往下走一層,談一道更隱形、卻更常被做壞的關卡:鍵盤操作與焦點管理(keyboard navigation & focus management)。對只用鍵盤、用開關輔具、用螢幕閱讀器的人來說,焦點(focus)就是他們的滑鼠游標。游標不見了、跳錯地方、被關進去出不來,整個介面就等於當機。
📖 學
先把心智模型建立好。鍵盤使用者靠三組動作在頁面上移動:Tab / Shift+Tab 在可聚焦元件之間前進後退;方向鍵在同一個元件內部移動(例如選單、分頁列、下拉選項);Enter / Space 觸發、Esc 退出。WCAG 2.1.1(Keyboard,Level A)的底線很簡單:所有連結、按鈕、表單欄位、下拉、對話框、輪播控制、手風琴(accordion)、分頁面板等互動元件,都必須能只用這幾顆鍵抵達並操作。這條線看似基本,卻是自動化掃描最難抓、真人測試最容易發現的問題——把滑鼠收起來,用鍵盤走一遍你的頁面,是成本最低的無障礙檢查。
第一個常見坑是焦點順序(focus order)。WCAG 2.4.3 要求焦點移動的順序要符合意義與操作邏輯。麻煩的是 CSS 常常讓「視覺順序」和「DOM 順序」脫節:你用 flex 的 order 或 grid 把某個按鈕在畫面上挪到右上角,但它在 DOM 裡還排在最後,鍵盤焦點就會跳得莫名其妙。原則是讓 DOM 順序反映閱讀與操作順序,把版面調整交給排版、而不是靠 tabindex 硬喬。順帶一提,tabindex 只該用兩個值:0(把自訂元件納入自然 tab 序)與 -1(移出 tab 序、但保留可用 JS 程式化聚焦的能力)。正整數的 tabindex 會強行插隊、打亂全站順序,幾乎永遠是錯的。
第二個坑是看不見的焦點。焦點若沒有明顯的視覺指示,鍵盤使用者根本不知道自己站在哪。WCAG 2.4.7(Focus Visible)要求鍵盤可操作的介面必須有可見的焦點指示器;2026 的實務共識是,焦點框與背景之間至少要有 3:1 對比,別再用 outline: none 一鍵抹掉瀏覽器預設外框卻不補回來。現代寫法用 :focus-visible 這個偽類:它讓瀏覽器只在「以鍵盤操作」時顯示焦點框、用滑鼠點擊時不顯示,一次解決了「開發者嫌焦點框醜」與「鍵盤使用者需要焦點框」的長年對立。
第三,也是最需要寫程式主動介入的,是焦點管理(focus management)——在動態內容變化時,程式化地決定焦點該去哪。最典型的場景是對話框(modal):打開時,焦點要移進對話框內第一個可操作元件(或對話框容器本身);開啟期間,Tab / Shift+Tab 必須被限制在對話框內循環,不能洩漏到後面的頁面,這就是焦點陷阱(focus trap);關閉時,無論怎麼關,焦點都要回到當初觸發它的那顆按鈕,讓使用者不會迷路。同一套邏輯也適用於 SPA 路由切換(換頁後把焦點移到新頁的主標題或 main)、表單送出後的錯誤摘要、以及展開/收合的動態區塊。
這裡要澄清一個矛盾:「焦點陷阱」聽起來像是 WCAG 2.1.2(No Keyboard Trap)禁止的東西,其實不然。2.1.2 禁止的是「進得去、出不來」的意外陷阱——例如某個嵌入元件吃掉了所有按鍵讓你逃不出去。而對話框的焦點鎖定是**刻意、且提供了明確出口(Esc 或關閉鈕)**的設計,兩者是一體兩面:一定要能用 Esc 關閉,焦點才能放行。
好消息是 2026 年你不必再手刻這一切。原生 <dialog> 元素配 showModal() 已是最穩健的做法:瀏覽器自動處理焦點陷阱、Esc 關閉、::backdrop 遮罩,幾乎不用寫 JS 管焦點。要把背景頁面「凍結」不讓螢幕閱讀器與鍵盤觸及,則用 inert 屬性(套在對話框以外的容器上),它會一次移除該子樹的可聚焦性與可讀性,比舊時代到處灑 aria-hidden 乾淨得多。搭配 ARIA 的 role="dialog"、aria-modal="true"、aria-labelledby / aria-describedby,螢幕閱讀器就能拿到完整語境。
最後補一條 WCAG 2.2 的新規:2.4.11(Focus Not Obscured,Minimum,Level AA)。它要求元件獲得焦點時,不能被其他內容完全遮住——最常見的兇手就是固定在畫面上的 sticky header / footer,把剛剛 tab 到的欄位壓在底下看不見。設計貼齊視窗邊緣的浮動元件時,要記得替焦點目標留出捲動的餘裕(例如用 scroll-margin)。
🧠 記
- 焦點就是鍵盤使用者的游標:看不見、跳錯、關不掉,介面就等於當機。
- 三組鍵:
Tab/Shift+Tab跨元件、方向鍵在元件內、Enter/Space/Esc觸發與退出。 tabindex只用 0 與 -1,正整數插隊會毀掉全站順序;版面靠排版、順序靠 DOM。:focus-visible讓焦點框只在鍵盤操作時出現;焦點對比至少 3:1,別裸奔outline: none。- 對話框三步:開→焦點進去、中→焦點鎖住(有
Esc出口)、關→焦點回到觸發鈕。 - 用原生
<dialog>+inert,少手刻;WCAG 2.2 新增 2.4.11 提醒別讓 sticky 元件遮住焦點。
✍️ 實踐
今天挑一個你手上的介面,做「拔滑鼠測試」:
- 把滑鼠移開,只用
Tab從頁首走到頁尾。記下三件事——焦點框看得見嗎?順序合理嗎?有沒有卡住出不來的地方? - 找一個對話框或彈出層,測三個時刻:打開後焦點在哪?開啟時
Tab會不會漏到背景?按Esc能關嗎、關掉後焦點回到觸發鈕嗎?任何一個「否」都記下來。 - 把全站
outline: none搜出來,改成:focus-visible版本並補上 ≥3:1 對比的焦點樣式。 - 若有 sticky header,tab 到頁面中段的欄位,確認它沒有被壓在標題底下;需要的話補
scroll-margin-top。
把找到的問題寫成一張清單,這就是你今天最實在的無障礙修補待辦。
🔗 延伸學習
- WAI-ARIA Authoring Practices — Dialog (Modal) Pattern:對話框焦點管理的權威範式與鍵盤互動清單。
- WCAG 2.2 Understanding 2.4.11 — Focus Not Obscured (Minimum):理解 sticky 遮擋焦點的新規與判準。
- MDN —
:focus-visible:鍵盤/滑鼠差異化焦點框的標準寫法。 - How to Build Accessible Modals with Focus Traps (2026 Guide) — UXPin:以
<dialog>與inert為主的現代實作導覽。
💬 問 AI
我要替一個 React SPA 做鍵盤無障礙全面體檢。請幫我:
1. 列一份「拔滑鼠」手動測試腳本,涵蓋焦點順序、焦點可見性、
對話框開/鎖/關三時刻,以及 SPA 路由切換後的焦點落點。
2. 給我一個以原生 <dialog> + inert + :focus-visible 實作的可存取對話框
最小範例,並標註對應的 WCAG 準則(2.1.1、2.1.2、2.4.3、2.4.7、2.4.11)。
3. 指出 3 個開發者最常犯、自動化掃描抓不到的焦點管理錯誤,
以及各自的修法。