跳到主要內容
全部文章

2026年9月15日·閱讀約 2 分鐘

表格標題無障礙:大多數網站遺漏的 WCAG 規則

沒有真正標題標記的資料表格,在螢幕閱讀器裡讀起來就是毫無意義的數字。瞭解 WCAG 1.3.1 的修復方法,以及 Axeazy 能與不能自動修復甚麼。

wcagaccessibilitytablestable-headerswcag-1-3-1axe-corescreen-readerhtml

沒有真正標題標記的資料表格,在螢幕閱讀器裡讀起來就是一串光禿禿的數字,完全不知道每個數字屬於哪一行或哪一列。 修復的辦法是:標題單元格用 <th> 代替 <td>,並新增 scope 屬性。這滿足 WCAG 1.3.1(資訊和關係),讓表格從單純的視覺排版變成螢幕閱讀器真正可以瀏覽的內容。

一個視覺上看起來正常、但螢幕閱讀器讀出來是無標籤數字的資料表格。

要點

  • 如果樣式相同,<th> 和 <td> 看起來一模一樣,但只有 <th> 會告訴螢幕閱讀器「這是標題」。 <td> 里加粗居中的文字,無論看起來如何,對輔助技術來說都不是標題。
  • scope 屬性告訴螢幕閱讀器標題適用於哪個方向。 有效值只有 col、row、colgroup 和 rowgroup,沒有其他。
  • 這是 WCAG 1.3.1(資訊和關係)的要求,A 級。 它還對應 Section 508 的 1194.22(g) 和 EN 301 549 的 9.1.3.1,因此不只是美國的規則。
  • axe-core 會掃描七種不同的表格問題,其中只有一種(拼錯的 scope 值)目前有自動修復。其餘的都需要一個懂得表格實際含義的人。
  • 簡單表格(標題只在第一行或第一列)所需的標記比大多數指南暗示的要少。 那裡只用普通的 <th> 元素就夠了;當表格在不止一個方向上有標題時,scope 才變得必要。

為甚麼 <th> 與 <td> 的區別就是一切

兩個看起來一模一樣的表格標題,一個用 td 構建,一個用 th 構建,只有一個會被螢幕閱讀器識別為標題。

視力正常的訪客閱讀表格時,會不假思索地掃過一行,再向上看列標題。螢幕閱讀器沒有等效的「抬眼一瞥」的辦法。它逐個單元格朗讀,而它知道某個單元格是標題而非普通資料的唯一方式,就是底層的 HTML 元素。

<th> 是標題單元格。瀏覽器預設將其加粗並居中,螢幕閱讀器在使用者瀏覽表格時會將其讀作標題。<td> 是普通資料單元格。無論對它應用甚麼 CSS,它都不會帶有這樣的提示。

幾乎所有現實中的表格都壞在這裡。開發人員做了一個表格,想讓標題行看起來加粗,於是用 <td style="font-weight: bold"> 而不是 <th>。視覺上,它與真正的標題無法區分。對螢幕閱讀器來說,它只是另一個碰巧加粗的資料單元格,與下方的單元格毫無關係。

<!-- 失敗:看起來像標題行,卻被讀作普通資料 -->
<table>
  <tr>
    <td><strong>Day</strong></td>
    <td><strong>Open</strong></td>
    <td><strong>Close</strong></td>
  </tr>
  <tr>
    <td>Monday</td>
    <td>9:00 AM</td>
    <td>6:00 PM</td>
  </tr>
</table>

<!-- 透過:真正的標題單元格,被讀作標題,外加表格標題說明 -->
<table>
  <caption>Store hours</caption>
  <tr>
    <th scope="col">Day</th>
    <th scope="col">Open</th>
    <th scope="col">Close</th>
  </tr>
  <tr>
    <td>Monday</td>
    <td>9:00 AM</td>
    <td>6:00 PM</td>
  </tr>
</table>

兩者對視力正常的訪客呈現完全一樣。只有第二個能告訴螢幕閱讀器使用者,「9:00 AM」屬於 「Monday」 的 「Open」。

scope 屬性,以及甚麼時候真正需要它

示意圖:在資料表格中,scope=col 指向一整列,scope=row 指向一整行。

對於簡單表格,也就是標題只在第一行,或只在第一列,而不是兩者都有的表格,只用 <th> 就夠了。螢幕閱讀器可以正確推斷方向。在這種情況下,WCAG 的嚴格要求其實比許多無障礙內容暗示的要輕。

一旦表格同時在兩個方向上有標題,例如日程表上方是星期、側邊是時間段,scope 在實踐中就不再是可選的了。沒有它,螢幕閱讀器沒有可靠的辦法知道某個 <th> 標註的是它所在的行還是列。

<table>
  <caption>Weekly class schedule</caption>
  <tr>
    <th scope="col"></th>
    <th scope="col">Monday</th>
    <th scope="col">Wednesday</th>
  </tr>
  <tr>
    <th scope="row">9:00 AM</th>
    <td>Yoga</td>
    <td>Pilates</td>
  </tr>
  <tr>
    <th scope="row">6:00 PM</th>
    <td>Spin</td>
    <td>Yoga</td>
  </tr>
</table>

這裡,scope="col" 標記星期標題,scope="row" 標記時間標題。螢幕閱讀器使用者移到 「Pilates」 單元格時會同時聽到兩者:Wednesday、9:00 AM、Pilates。

對於真正不規則的表格,也就是有合併單元格、標題不整齊地位於某一行或某一列的表格,僅靠 scope 是不夠的。這就是 headers 和 id 屬性的用途:每個資料單元格明確列出適用於它的每個標題單元格的 id。

<th id="q3">Q3</th>
...
<td headers="q3">42</td>

這種寫法編寫和維護都更費功夫,這正是它只保留給 scope 確實無法描述的表格、而不作為預設做法的原因。

axe-core 究竟檢查表格的哪些方面

axe-core 就是 Chrome DevTools 無障礙面板和 Axeazy 掃描器背後的同一個引擎,它對錶格標記執行七項獨立檢查。目前只有一項有自動修復。

規則 檢查內容 修復路徑
scope-attr-valid scope 的值不是真正的單詞,是 col/row/colgroup/rowgroup 的拼寫錯誤或複數 透過 GitHub pull request 自動修復,範圍很窄:只處理可識別的拼寫錯誤
empty-table-header 存在 <th> 但裡面沒有文字 手動
th-has-data-cells 存在 <th> 但沒有任何資料單元格使用它 手動
td-has-header 某個資料單元格完全沒有關聯的標題 手動
td-headers-attr headers 屬性指向的 id 不是真正的標題單元格 手動
table-duplicate-name 同一頁面上的兩個表格具有相同的可訪問名稱 手動
table-fake-caption 文字被設定成看起來像標題說明,而不是真正的 <caption> 元素 手動

這張表讓真實情況一目瞭然。表格結構絕大部分屬於手動修復的類別。唯一自動寫入的部分,是更正已經存在但拼錯的 scope 值,比如把 "cols" 或 "ROW " 規範化為 "col" 或 "row"。新增缺失的標題、編寫表格標題說明,或確定哪個 headers 的 id 屬於哪個單元格,都需要理解表格的實際含義,這仍然是人的工作。

如何檢查您自己的表格

您不需要安裝螢幕閱讀器就能發現這個錯誤最常見的形式。

  1. 檢視頁面原始碼。 右鍵點選網站上的任意表格,選擇「檢視頁面原始碼」,或開啟 DevTools 檢視 Elements 面板。搜尋 <th。如果看起來像標題的那一行是由 <td> 元素構成的,這個表格就不透過。
  2. 檢查多方向表格上的 scope 值。 如果表格同時有橫向在上方和縱向在側邊的標題,請確認每個 <th> 都帶有 scope="col" 或 scope="row",而不只是加粗樣式。
  3. 聽一聽。 在 NVDA 中,用表格導航(Ctrl+Alt+方向鍵)在標記正確的表格中移動時,會在每個單元格的值旁讀出相關標題。如果您只聽到沒有先讀標題的原始數字,說明表格沒有被正確關聯。
  4. 或者執行掃描,而不是逐個表格檢查。 Axeazy 的免費掃描大約 60 秒就能按上述全部七條規則檢查您首頁上的每個表格,同時涵蓋它支援的其他修復類別。

常見錯誤

  • 用加粗的 <td> 單元格代替 <th>。 最常見的失敗,也最難靠肉眼發現,因為它看起來完全正確。
  • 表格標題說明只是表格上方一行加粗的文字,而不是真正的 <caption> 元素。視力正常的訪客看不出區別,螢幕閱讀器卻能。
  • 同一頁面上的多個表格沒有區分性的標題說明,導致螢幕閱讀器使用者在表格之間跳轉時分不清哪個是哪個。
  • 僅為視覺排版而使用表格,而不是用於表格資料。螢幕閱讀器仍會嘗試讀出本不該存在的行列關係,這比完全沒有結構更令人困惑。現代 CSS(Grid 或 Flexbox)可以處理排版,沒有這種副作用。
  • 在表格實際需要 headers/id 時卻使用 scope。 scope 只能描述簡單的行或列關係。有合併單元格或標題不整齊位於某一行或某一列的表格,需要更明確的 headers/id 配對。

Axeazy 在這裡能修復甚麼

流程圖:只有拼錯的 scope 值會被自動修復,其他所有表格無障礙問題都被標記為需手動稽核。

這一類別比大多數更適合坦率地談,因為其中自動化的部分確實很小。

Axeazy 的掃描一旦發現上述七條表格規則的問題就會標記出來,無論是 Axeazy、Chrome DevTools,還是任何基於 axe-core 的工具,檢測結果都是一樣的。寫出修復內容才是需要手動的地方。目前唯一能進入真實 GitHub pull request 的表格相關修復,是更正已存在的、屬於可識別拼寫錯誤或複數的 scope 值,該類別中的其他任何問題都還沒有自動化路徑。

目前 WordPress 外掛也完全沒有處理表格修復的途徑。免費外掛的三項即時修復涵蓋跳過導航、語言屬性和替代文字後備,都不涉及表格標記,付費方案目前也沒有擴充套件到表格結構。

這不是有人遺忘的缺口。判斷某個 <td> 是否本應是 <th>,或者一個不規則單元格究竟屬於哪個標題,需要理解表格想要表達甚麼。指令碼在這裡猜錯,會悄悄地向恰恰依賴標記來理解資料的那些人歪曲資料,這比標記出來交給人正確修復更糟。檢視完整的修復類別說明。

常見問題

每個標題單元格都需要 scope 屬性嗎?

不需要。對於標題只在第一行或只在第一列的簡單表格,普通的 <th> 元素本身就足夠了。當表格同時在兩個方向上有標題時,scope 才變得必要,比如上方是星期、側邊是時間的日程表。

scope 與 headers/id 有甚麼區別?

scope 描述簡單的單方向關係:一個標題適用於整行、整列,或一組行或列。headers/id 更明確:每個資料單元格透過 id 精確列出哪些標題單元格適用於它。規則的網格狀表格用 scope,只有帶合併單元格或標題不整齊位於某行某列的不規則表格才用 headers/id。

Axeazy 會自動修復缺失的表格標題嗎?

大多數不會,值得坦率地說明。目前表格相關修復中唯一有可用自動路徑的,是更正已存在但拼錯的 scope 值。新增缺失的標題、編寫真正的表格標題說明,或把 headers 對映到正確的 id,都需要理解表格的含義,因此它們仍屬於手動稽核項。

每個表格都需要表格標題說明(caption)嗎?

它本身並非 WCAG 1.3.1 的嚴格要求,但確實有用,尤其是當頁面上有不止一個表格時。表格標題說明為表格提供可訪問名稱,讓在表格之間跳轉的螢幕閱讀器使用者知道自己到了哪一個。

用於頁面佈局而不是資料的表格呢?

儘可能避免。螢幕閱讀器仍會嘗試讀出佈局表格上的行列關係,造成用純 CSS 佈局時不會有的困惑。如果遺留的佈局表格無法立即移除,給它加上 role="presentation" 可以讓輔助技術完全跳過表格語義。

參考來源

想在電郵中收到這類內容嗎?

The ADA Alert。每週資訊。

逢星期二:無障礙新聞、常見 WCAG 問題及程式碼層面的修正。免費。一鍵取消訂閱。 本電子報以英文撰寫。

我們交付的每份報告的範圍: 範圍限制:本報告記錄的是使用 axe-core v4.11.0 進行 WCAG 2.2 AA 自動掃描所發現的違規問題。自動化工具無法測試所有 WCAG 準則;許多準則需要人工審查。本報告不構成法律意見,不保證符合 ADA,亦不能防止法律行動。是否符合 ADA 是由法院作出的法律裁定。如需完整的 WCAG 合規評估,請聘請獲 IAAP 認證的無障礙專業人士。