全域屬性 tabindex 控制元素是否可以取得焦點,以及如何參與循序鍵盤導覽。
使用者通常以 Tab 移至下一個可取得焦點的元素,並以 Shift + Tab 向後移動。
<div tabindex="0">
這個元素可以取得鍵盤焦點。
</div>不過,讓元素可以取得焦點,不會自動令它具有互動功能或符合無障礙需要。
如果原生控制元素符合預期行為,應優先使用 <button>、<a href> 和 <input> 等元素。
36.1 主要值
| 值 | 意思 |
|---|---|
0 | 將元素加入一般循序焦點次序 |
-1 | 將元素排除於循序焦點導覽之外,但允許透過程式碼設定焦點 |
| 正整數 | 將元素排在 tabindex="0" 及一般原生 Tab 停留點之前 |
雖然可以使用其他負整數,但負值模式應使用 -1。
大部分應用程式應只使用 0 和 -1。正值通常會令鍵盤導覽更難維護及理解。
實際鍵盤行為可能因瀏覽器、作業系統設定及元素而有所不同。
36.2 原生可取得焦點的元素
許多 HTML 元素已經支援鍵盤焦點:
<a href="course.html">查看課程</a>
<button type="button">開啟預覽</button>
<label for="email">電郵</label>
<input id="email" type="email">這些元素通常不需要 tabindex="0"。
原生按鈕也提供:
- 按鈕語意。
- 鍵盤啟動行為。
- 停用行為。
- 與表單及輔助技術的整合。
相比之下:
<div tabindex="0">開啟預覽</div>這只會建立可以取得焦點的元素,不會建立按鈕,也不會自動像按鈕一樣回應 Enter 或空白鍵。
36.3 tabindex="0"
使用 tabindex="0",將原本不能取得焦點的元素加入一般循序焦點次序:
<div tabindex="0" role="region" aria-label="可捲動範例">
<!-- 內容 -->
</div>適當用途之一,是鍵盤使用者需要操作的可捲動區域。
另一種用途,是自訂複合控制元件中的目前項目,例如採用移動式 tabindex 模式的分頁清單。
不要為每個標題、段落或容器加入 tabindex="0"。輔助技術通常可以透過閱讀指令存取靜態內容,不需要額外 Tab 停留點。
每個新增的 Tab 停留點都應具有清晰用途。
36.4 tabindex="-1"
如果元素需要透過程式碼取得焦點,但不應成為一般 Tab 停留點,可以使用 tabindex="-1":
<h2 id="results-heading" tabindex="-1">搜尋結果</h2>document.getElementById("results-heading").focus();常見用途包括:
- 在使用者主動導覽後,將焦點移至標題。
- 提交表單後,將焦點移至錯誤摘要。
- 管理自訂控制元件內的焦點。
- 為跳過連結模式提供可靠目標。
tabindex="-1" 不會隱藏或停用元素。
它也不保證指標操作無法令元素取得焦點。這種行為取決於元素及瀏覽器。
36.5 避免正值
正值會建立獨立的優先次序:
<button type="button" tabindex="2">第二優先</button>
<button type="button" tabindex="1">第一優先</button>
<button type="button">一般次序</button>即使 tabindex="1" 的按鈕在原始碼中較後出現,它仍然會先於 tabindex="2" 的按鈕取得焦點。
正值按由小至大的次序處理。具有相同正值的元素,通常按文件次序排列。之後才輪到一般 Tab 停留點及 tabindex="0" 的元素。
內容變更時,這種做法很容易出問題。
應修正文件結構,而不是為可取得焦點的元素編號。
36.6 焦點次序與視覺次序
一般焦點次序通常遵循文件次序,而不是 CSS 建立的視覺位置。
例如,Flexbox 或 Grid 可以改變內容的視覺排列,但鍵盤導覽仍然使用不同次序:
.actions {
display: flex;
}
.actions .last-in-source {
order: -1;
}不要透過正值 tabindex 補救。
應:
- 按合乎邏輯的次序排列原始碼內容。
- 讓視覺排列與該次序保持一致。
- 使用 Tab 及 Shift + Tab 測試網頁。
- 確保焦點次序保留內容意思及可操作性。
焦點次序不必對應每個視覺細節,但應可預測且容易理解。
36.7 可取得焦點不等於具有互動功能
以下不是完整的自訂按鈕:
<div tabindex="0" role="button">儲存</div>role="button" 傳達角色,tabindex="0" 允許取得焦點,但兩者都不會實作按鈕行為。
作者仍然需要處理鍵盤啟動、指標輸入、停用狀態及其他細節。
應優先使用:
<button type="button">儲存</button>同樣,為圖片加入 tabindex 不會令它變成連結,為容器加入它也不會令容器變成符合無障礙需要的應用程式。
建立自訂控制元素前,應先考慮原生 HTML。
36.8 可見焦點提示
鍵盤使用者需要看見目前哪個元素具有焦點。
使用 :focus-visible 提供清晰提示:
:focus-visible {
outline: 3px solid #005ea8;
outline-offset: 3px;
}不要移除瀏覽器焦點外框,卻沒有提供有效替代:
/* 如果沒有替代焦點提示,這種寫法並不適當。 */
:focus {
outline: none;
}應檢查提示是否:
- 具有足夠對比度。
- 沒有被周圍容器裁切。
- 在高對比度或強制色彩模式下保持可見。
- 沒有被固定頁首或其他覆蓋內容遮擋。
可以取得焦點卻沒有可見焦點提示的元素,很難操作。
36.9 透過程式碼設定焦點
使用 .focus() 移動焦點:
const heading = document.getElementById("results-heading");
heading.focus();標題通常需要 tabindex="-1" 才能支援這種模式。
如果不希望設定焦點時捲動網頁:
heading.focus({ preventScroll: true });只有在目標仍然可見,或另外管理捲動時,才使用這個選項。
應因應有意義的使用者操作或應用程式變更移動焦點。不要在網頁載入時,或使用者輸入期間,不必要地搶走焦點。
替換內容時,應考慮目前具有焦點的元素是否會被移除。失去該元素可能令使用者無法確定目前位置。
36.10 跳過連結
跳過連結讓鍵盤使用者略過重複導覽:
<a href="#main-content" class="skip-link">跳至主要內容</a>
<header>
<!-- 網站導覽 -->
</header>
<main id="main-content" tabindex="-1">
<h1>課程資訊</h1>
</main>負值讓主要區域可以取得焦點,但不會成為額外的一般 Tab 停留點。
設定樣式,讓連結在取得焦點時顯示:
.skip-link {
position: absolute;
inset-inline-start: 1rem;
top: 1rem;
transform: translateY(-200%);
}
.skip-link:focus {
transform: translateY(0);
}應測試啟動連結後,是否將使用者帶到預期內容,以及焦點是否被遮擋。
36.11 移動式 tabindex
部分複合控制元件會讓整個元件只佔用一個 Tab 停留點。
在**移動式 tabindex(roving tabindex)**模式中:
- 一個項目具有
tabindex="0"。 - 其他項目具有
tabindex="-1"。 - 方向鍵在項目之間移動焦點。
- JavaScript 更新哪個項目具有
tabindex="0"。
如果按照預期鍵盤互動方式實作,這種模式可以適用於分頁或工具列等控制元件。
單靠屬性不會實作這個模式。應用程式也必須管理:
- 適當角色及狀態。
- 鍵盤指令。
- 焦點移動。
- 選取或啟動行為。
- 適用情況下的停用項目。
部分控制元件也可以使用 aria-activedescendant 作為另一種焦點管理方式。
沒有清晰理由時,不要將這些模式套用至一般網站導覽。標準連結通常使用原生行為最合適。
36.12 隱藏、停用與 Inert 內容
tabindex 不能取代可見性或停用狀態管理。
| 功能 | 一般效果 |
|---|---|
tabindex="-1" | 將元素排除於一般循序焦點導覽之外 |
支援控制元素上的 disabled | 停用控制元素,通常也將它排除於焦點導覽之外 |
hidden 或 display: none | 通常阻止呈現及取得焦點 |
inert | 阻止子樹內的互動及焦點,並將它排除於無障礙資訊之外 |
aria-hidden="true" | 對無障礙 API 隱藏內容,但本身不會阻止取得焦點 |
aria-disabled="true" | 傳達停用狀態,但本身不會停用行為 |
不要在標示為 aria-hidden="true" 的內容內留下可取得焦點的控制元素。
模態介面應在適當情況下優先使用原生 <dialog>,並測試焦點位置、焦點限制、關閉及焦點還原。不要透過任意正值臨時拼湊焦點限制機制。
36.13 完整範例:跳過連結與可捲動程式碼
以下獨立網頁示範兩種具有明確用途的做法:
- 在跳過連結目標上使用
tabindex="-1"。 - 在可水平捲動的程式碼範例上使用
tabindex="0"。
原生連結及按鈕保留一般焦點行為。
id: hong-kong-html-tabindex
name: tabindex — 跳過連結與可捲動範例
type: html
content: |-
<!doctype html>
<html lang="zh-Hant-HK">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta
name="description"
content="示範如何為跳過連結及可捲動程式碼區域適當使用 tabindex。"
>
<title>tabindex | 鍵盤導覽範例</title>
<style>
*,
*::before,
*::after {
box-sizing: border-box;
}
body {
margin: 0;
color: #222;
background: #f5f7fa;
font-family: system-ui, sans-serif;
line-height: 1.8;
}
header,
main {
max-width: 44rem;
margin-inline: auto;
padding: 1.5rem 1rem;
}
header {
padding-block-end: 0;
}
nav ul {
display: flex;
flex-wrap: wrap;
gap: 0.5rem 1.5rem;
margin: 0;
padding: 0;
list-style: none;
}
h1,
h2 {
color: #005ea8;
line-height: 1.4;
}
section {
min-width: 0;
margin-block: 1.5rem;
padding: 1.25rem;
border: 1px solid #767676;
border-radius: 0.5rem;
background: white;
}
section > h2 {
margin-block-start: 0;
}
a {
color: #005ea8;
text-underline-offset: 0.2em;
}
button {
padding: 0.6rem 1rem;
border: 1px solid #005ea8;
border-radius: 0.3rem;
color: white;
background: #005ea8;
font: inherit;
cursor: pointer;
}
pre {
max-width: 100%;
overflow-x: auto;
padding: 1rem;
border: 1px solid #767676;
border-radius: 0.3rem;
background: #f5f7fa;
font-size: 1rem;
white-space: pre;
}
code {
font-family: ui-monospace, monospace;
}
:focus-visible {
outline: 3px solid #005ea8;
outline-offset: 3px;
}
main:focus {
outline: 3px solid #005ea8;
outline-offset: 3px;
}
.skip-link {
position: absolute;
inset-inline-start: 1rem;
top: 1rem;
z-index: 10;
transform: translateY(-200%);
padding: 0.65rem 1rem;
border: 2px solid #005ea8;
color: #005ea8;
background: white;
}
.skip-link:focus {
transform: translateY(0);
}
[id] {
scroll-margin-block-start: 1rem;
}
</style>
</head>
<body>
<a class="skip-link" href="#main-content">
跳至主要內容
</a>
<header>
<nav aria-label="頁面區段">
<ul>
<li><a href="#instructions">操作指引</a></li>
<li><a href="#example">程式碼範例</a></li>
</ul>
</nav>
</header>
<main id="main-content" tabindex="-1">
<h1>有明確用途的鍵盤焦點</h1>
<section aria-labelledby="instructions">
<h2 id="instructions">操作指引</h2>
<ol>
<li>按 Tab 顯示跳過連結。</li>
<li>啟動連結,移至主要內容。</li>
<li>繼續按 Tab,移至程式碼區域。</li>
<li>如果程式碼超出區域寬度,可使用方向鍵捲動。</li>
<li>再次按 Tab,移至原生按鈕。</li>
</ol>
<p>
主要區域可以取得焦點,
但不是一般 Tab 停留點。
</p>
</section>
<section aria-labelledby="example">
<h2 id="example">可捲動程式碼範例</h2>
<p id="code-help">
這個區域可以取得鍵盤焦點,
讓超出寬度的內容可以捲動。
按 Tab 繼續移動。
</p>
<pre
tabindex="0"
role="region"
aria-label="JavaScript 原始碼範例"
aria-describedby="code-help"
><code translate="no">const message = "This deliberately long line demonstrates horizontal scrolling in a focusable code region.";
console.log(message);</code></pre>
<button type="button" id="confirm-button">
確認鍵盤操作
</button>
<p id="confirmation" role="status"></p>
</section>
</main>
<script>
const button = document.getElementById("confirm-button");
const confirmation = document.getElementById("confirmation");
button.addEventListener("click", () => {
confirmation.textContent =
"原生按鈕已啟動;此操作不會主動移動焦點。";
});
</script>
</body>
</html>在這個範例中:
- 跳過連結位於原始碼次序的最前方。
- 主要區域使用
tabindex="-1"作為焦點目標。 - 可捲動程式碼區域使用
tabindex="0"。 - 原生連結及按鈕不需要額外
tabindex。 - 焦點提示保持可見。
- 啟動按鈕會更新狀態訊息,不會主動移動焦點。
- 不需要正值或自訂鍵盤啟動處理程式。
應使用目標瀏覽器及鍵盤設定測試,因為捲動及片段連結的焦點行為可能有所不同。
36.14 常見錯誤
| 錯誤 | 較好的做法 |
|---|---|
| 使用正值重新排列網頁焦點次序 | 修正原始碼及視覺次序 |
| 讓每個容器都成為 Tab 停留點 | 只有在用途清晰時才加入焦點能力 |
將可取得焦點的 <div> 當成完整按鈕 | 使用 <button> |
以為 -1 會停用元素 | 使用適當的停用或 inert 機制 |
| 移除焦點外框,卻沒有替代提示 | 提供可見焦點提示 |
| 意外移動焦點 | 將焦點變更與有意義的操作關聯 |
| 將焦點移至仍在螢幕範圍外的內容 | 協調焦點及捲動 |
在 aria-hidden 區域內留下可取得焦點的內容 | 移除焦點能力,或使用適當隱藏方式 |
| 使用移動式 tabindex,卻沒有鍵盤邏輯 | 實作完整控制元件模式 |
任意使用 tabindex 修補模態介面 | 使用適當對話框及焦點管理方式 |
| 只使用指標測試 | 測試 Tab、Shift + Tab 及鍵盤啟動 |
本章總結: tabindex 管理焦點參與方式,不會提供完整互動行為。應優先使用原生控制元素,以 0 提供有明確用途的循序焦點,以 -1 提供受管理的焦點目標,並避免正值。保持合乎邏輯的次序、可見焦點及可預測的鍵盤互動。















