大型資料表在小螢幕上往往難以閱讀,因為資料欄會溢出或縮小到無法辨識。本指南將示範如何使用固定位置,在捲動時保持標頭可見,以及如何在空間有限時,將表格轉換為適合行動裝置的「堆疊」版面配置。
建議做法
回應式表格的核心,在於維持資料儲存格與標題之間的關係。
- 語意基礎:使用標準
<table>元素搭配<thead>、<tbody>和<th>元素。 - 固定背景資訊:將
position: sticky同時套用至欄標題和列標題。這樣一來,無論使用者往任何方向捲動多遠,都不會忘記資料代表的意義。 - Adaptive 轉換:使用
@container查詢,而非@media查詢。這樣一來,表格就能根據自身寬度 (例如放在側欄或窄版資訊主頁小工具中) 調整,而不是根據整個可視區域。 - 標籤插入:在堆疊版面配置中,
<thead>會隱藏,並使用::before虛擬元素和 CSS 變數,將無障礙標頭插入每個儲存格。
導入步驟
1. 標記和版面配置
使用標準語意標頭建構表格。使用 .table-wrapper 處理溢位,並為查詢提供容器。在容器中加入 tabindex="0" 屬性,確保只使用鍵盤的使用者能順利捲動表格。
為包裝函式提供 role="region" 屬性,以傳達其用途。使用 aria-labelledby (參照現有元素的 id 屬性,為表格加上標題/名稱) 或 aria-label 屬性,為表格提供無障礙元素名稱。
<div tabindex="0" role="region" aria-labelledby="employee-table-caption" class="table-wrapper">
<table>
<caption id="employee-table-caption">Current employee directory</caption>
<thead>
<tr>
<th>Employee</th>
<th>Role</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<tr>
<!-- Row header remains sticky horizontally on desktop,
and becomes a sticky card header on mobile -->
<th>Alex Rivera</th>
<td>Engineer</td>
<td>Active</td>
</tr>
</tbody>
</table>
</div>
2. 設定固定標頭
啟用捲動功能並固定標頭。使用邏輯屬性和明確的 z-index 值,管理堆疊順序。
.table-wrapper {
overflow: auto;
max-inline-size: 100%;
max-block-size: min(500px, 80vh);
container-type: inline-size;
}
table {
border-collapse: separate;
border-spacing: 0;
}
/* Sticky column headers (desktop) */
thead {
position: sticky;
inset-block-start: 0;
z-index: 1; /* Above sticky row headers */
}
/* Sticky row headers (desktop) */
tbody th {
position: sticky;
inset-inline-start: 0;
background: white; /* Required to cover background during scroll */
}
3. 回應式堆疊版面配置
如果空間有限,請隱藏原始標題列,並將表格轉換為資訊卡。使用虛擬元素插入標籤。請只為非文字內容提供替代文字,否則螢幕閱讀器可能會同時朗讀文字內容和無障礙元素名稱。
@container (width < 600px) {
table, thead, tbody, tr, th, td {
display: block;
}
thead {
/* MANDATORY: Hide column header from layout and screen readers. */
display: none
}
tr {
margin-block-end: 1.5rem;
border: 1px solid #ccc;
}
/* Sticky row headers at the top of each block/card */
tbody th {
position: sticky;
inset-block-start: 0;
z-index: 2;
background: #eee;
}
/* Define column labels as CSS variables (often set via JS) */
table {
--label-1: "Employee";
--label-2: "Role";
--label-3: "Status";
}
/* Inject labels */
td:nth-child(2)::before {
/* MANDATORY: If using non-text content, provide the accessible name (after the /) which ensures screen readers hear the text alternative. */
content: var(--label-2) ": ";
}
/* MANDATORY: Map the label for each column */
td:nth-child(3)::before {
content: var(--label-3) ": ";
}
}