iT邦幫忙

2026 iThome 鐵人賽

DAY 29
0
Modern Web

從實務需求出發:前端視覺與 UI 互動開發實踐系列 第 29 篇

【Day 29】最後補充:無障礙網頁設計

  • 分享至 

  • xImage
  •  

前面這幾天做了拖曳、排序動畫、滾動進場、視差,每一個都很吸睛。但是它們有個共同的前提:使用者看得到畫面、手上有滑鼠、也不介意畫面一直在動。只要其中一項不成立,這些效果就可能變成反效果。

其實我自己還沒針對無障礙優化過專案,但無論是前端熱門套件,還是 AI agent 寫 code 都能看到無障礙設計。所以今天來具體了解一下無障礙網頁到底是什麼,並整理出前端需要注意的重點。文章分成三段:先搞懂什麼是無障礙,再看 HTML 怎麼做,最後是 CSS 與 JavaScript 該注意什麼。那麼我們開始吧!


一、什麼是無障礙網頁

依照 MDN 的說法,無障礙網頁就是讓盡可能多的人都能使用網站,不論能力或環境如何。實際範圍比想像中大,不只侷限於身心障礙者,連老化、暫時性的受傷(手臂骨折用不了滑鼠)、情境限制(太陽底下看手機、網速很慢)都算在內。

MDN 又將這些使用者分成四類:

類型 常見輔助科技 我們要做的
視覺 螢幕閱讀器(NVDA、VoiceOver、TalkBack)、螢幕放大 替代文字、語意化 HTML、可縮放的版面
聽覺 - 影音提供字幕與文字稿
行動/肢體 鍵盤、其他替代輸入裝置 所有功能都能用鍵盤操作
認知 - 版面一致、流程簡單、清楚的錯誤訊息

認知類這方面,最近剛好深有感觸。長輩有個帳戶要確認是否本人使用,只需要填寫簡單的基本資料、送出就可以了。以長輩的 3C 能力來說綽綽有餘,但是她自己試了一個下午都不行。最後我一看,原來是前兩步的確認按鈕在左,取消按鈕在右;最後一步的確認按鈕卻反過來在右。再加上兩顆按鈕顏色一樣、按到取消也沒有反饋,一不小心就陷入無限確認取消地獄。

WCAG:判斷標準

那要做到什麼程度算無障礙?目前最主要的國際標準是 W3C 發布的 WCAG(Web Content Accessibility Guidelines)。

  • 四大原則,簡稱 POUR:可感知(Perceivable)、可操作(Operable)、可理解(Understandable)、健壯(Robust)。
  • 三個等級 A、AA、AAA,實務上多以 AA 為目標。

目前最新的版本是 WCAG 2.2,2023 年 10 月成為 W3C Recommendation,在 2.1 的基礎上新增了 9 項成功準則,並移除了 4.1.1 Parsing。其中與系列文章中做過的東西最有關的有三項(皆為 AA):

  • 2.5.7 拖曳動作:用拖曳完成的功能,也要能用單一指標、不需要拖曳來完成(拖曳本身是必要時例外)。例如 Day25 地點配對範例,可以加上「點選項目、再點選目標」的操作。
  • 2.5.8 目標尺寸(最低要求):指標操作的目標至少 24×24 CSS px,間距足夠、行內文字連結等情況例外。
  • 2.4.11 焦點不被遮蔽(最低要求):聚焦中的元件不能被固定 header、懸浮元素之類的內容「完全」遮住。

MDN 也一再強調,無障礙要及早納入專案,並當成一般功能來測試。不用做到 100%,但要盡力提高覆蓋率。

💡 台灣的無障礙規範:台灣的官方入口是數位發展部的無障礙網路空間服務網,提供規範、檢測工具與標章申請。可以參考官網的「網站無障礙規範」。

補充:無障礙樹

螢幕閱讀器讀的並不是畫面,而是瀏覽器根據 DOM 產生的無障礙樹(accessibility tree)。每個節點都有 role(是什麼)、name(叫什麼)、state(現在什麼狀態)。所以不用特別去設定,語意化 HTML 做對了,無障礙樹就會跟著對。可以開啟 Chrome DevTools 的 Elements 面板就有 Accessibility 分頁可以查看無障礙樹。

無障礙樹 Chrome DevTools


二、HTML 實踐:把原生元素用對

1. 用對元素,勝過加 ARIA

MDN 把它叫做 POSH(Plain Old Semantic HTML):正確的元素做正確的工作。最經典的對照就是按鈕:

<!-- 😢 要自己補 tabindex、role、Enter/Space 事件、各種狀態樣式 -->
<div class="btn" @click="save">儲存</div>

<!-- 😊 鍵盤聚焦、Enter/Space 觸發、螢幕閱讀器角色,全都內建 -->
<button type="button" @click="save">儲存</button>

💡 ARIA 補充:

  • 什麼是 ARIA? WAI-ARIA 是 W3C 制定的一組 HTML 屬性,用來替元素補上「是什麼(role)」、「叫什麼(aria-label)」、「現在什麼狀態(aria-expanded)」,讓螢幕閱讀器聽得懂。例如只有圖示的按鈕,可以加上 aria-label="關閉"。
  • 使用 ARIA 的第一守則:能用原生元素,就不要用 ARIA。ARIA 只補語意,不會補行為,它不會幫你加上鍵盤操作。(出處:Using ARIA | W3C)

另外,一般狀況 <a href> 是用來換頁的,<button> 是用來執行動作的,所以盡量不要寫成 <a href="#" onclick="...">。

2. 結構與地標(Landmarks)

  • 標題照層級走:h1~h6 是結構,不是字體大小。螢幕閱讀器可以用標題快速跳讀,不要為了想要的字級去挑標題層級。
  • 用語意化區塊:header、nav、main、aside、footer 讓使用者能直接跳到想去的區塊,一頁只放一個 main。
  • 原始碼順序要合理:螢幕閱讀器和 Tab 鍵走的是 DOM 順序,不是畫面順序。Grid 的 order、grid-area 重排只改變視覺,不會改變閱讀和 Tab 的順序。
  • 跳過連結(skip link):在頁首放一個「跳到主要內容」的連結,鍵盤使用者就不用每換一頁都要 Tab 過整排導覽列。
<body>
  <a class="skip-link" href="#main">跳到主要內容</a>
  <header>…導覽列…</header>
  <main id="main">…</main>
</body>

3. 圖片與替代文字

alt 要描述「這張圖在這個脈絡下傳達了什麼」。例如同一張狗狗照片,在留言區寫名字就好,若在領養頁面則要寫毛色、體型。裝飾用的圖片則寫成空的 alt="",讓螢幕閱讀器直接跳過。另外,不要把文字做進圖片裡,因為會讀不到,也無法選取複製。

像是之前做的 SVG 圖示,也是一樣的邏輯:

<!-- 有意義的圖示:給它名字 -->
<svg role="img" aria-label="已完成">…</svg>

<!-- 純裝飾:叫螢幕閱讀器跳過 -->
<svg aria-hidden="true" focusable="false">…</svg>

4. 表單與連結文字

  • 每個輸入欄位都要配一個 <label for>,placeholder 不能取代 label。
  • 連結文字要能「脫離上下文」被理解,不要寫「點這裡」。開新視窗、下載檔案,也要在文字裡說明。
  • 資料表格用 <th scope> 標明是 col 還是 row;用 <caption> 放表格的標題或摘要。
<label for="name">姓名</label>
<input id="name" name="name" type="text" />

<a href="report.pdf">下載 2026 年度報告(PDF)</a>

但如果設計稿上的連結文字就只有「下載」,也有辦法讓螢幕閱讀器念出完整名稱。有以下兩種做法:

<!-- 做法一:視覺隱藏文字,畫面上只看到「下載」 -->
<a href="report.pdf">
  下載<span class="visually-hidden">2026 年度報告(PDF)</span>
</a>

<!-- 做法二:卡片或列表內,用 aria-labelledby 指向標題 -->
<article>
  <h3 id="r1">2026 年度報告</h3>
  <a href="report.pdf" id="d1" aria-labelledby="d1 r1">下載</a>
</article>

5. 其他

  • 在 <html> 設定語言,例如 <html lang="zh-Hant-TW">,讓螢幕閱讀器用對的發音。
  • 只有圖示的按鈕一定要有名稱(aria-label 或隱藏的文字)。

三、CSS 與 JavaScript 實踐

HTML 打好地基之後,CSS 和 JS 該做的事是「不要破壞它」,還有補上互動的部分。

CSS:看得清楚、不拿掉回饋

1. 對比

WCAG AA 要求一般文字與背景的顏色對比至少 4.5:1(大字 3:1),UI 元件的邊框、圖示等則是 3:1。另外不要只靠顏色傳達資訊,例如錯誤欄位除了變紅色,還要有圖示或文字說明。如果有做深淺色主題,兩套主題都要檢查。比較簡單的檢查方式是用 DevTools 的對比顯示,點擊顏色旁的色塊後再點 Contrast ratio,就會顯示有沒有達到 AA 或 AAA 的標準。

Contrast ratio(對比值)

2. 焦點樣式不要拿掉

outline: none 是很容易踩到的雷,拿掉 outline 之後鍵盤使用者就不知道自己在哪裡了。如果需要客製化樣式,就用 :focus-visible:

:focus-visible {
  outline: 3px solid var(--focus-color);
  outline-offset: 2px;
}

/* 對應 WCAG 2.4.11:被固定 header 遮住的元素,捲動時留出空間 */
html {
  scroll-padding-top: 80px;
}

3. 隱藏內容要看對象

  • display: none 和 visibility: hidden 會讓視覺與螢幕閱讀器都看不到。
  • 如果想要「畫面上看不到、螢幕閱讀器讀得到」,要用 visually-hidden 的寫法(絕對定位加裁切),而不是 display: none。
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

4. 支援減少動態設定

有些使用者對畫面移動很敏感,會在系統設定開啟「減少動態效果」。CSS 可以用 prefers-reduced-motion 讀到這個設定。前面做的動畫、滾動進場、視差,最容易引起不適的就是大面積的位移和視差,需要優先處理,進場動畫可以改成直接顯示:

@media (prefers-reduced-motion: reduce) {
  .parallax,
  .fade-in {
    animation: none;
    transition: none;
  }
}

JS 也能讀到 prefers-reduced-motion 設定,偵測到的話,同樣讓進場動畫直接顯示就好:

const reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches

💡 WCAG 對「互動觸發的動畫可以關閉」的要求(2.3.3)是 AAA 等級,不是 AA。所以這比較像是優化對動態敏感使用者的體驗,而不是通過 AA 的必要條件。

其他還有:

  • 版面要能放大:文字放大到 200% 內容不能被截掉(WCAG 1.4.4);視窗縮到 320px 寬時不該出現橫向捲動(WCAG 1.4.10)。比較常見的方式是字級單位用 rem,而不是寫死的 px,可以讓使用者調整瀏覽器的預設字級時也跟著變。
  • 按鈕與連結至少 24×24 px(WCAG 2.2 的 2.5.8),行動裝置建議再更大一點。

說個鬼故事,之前遇過一個設計稿,行動版越切越不對勁,後來發現行動版的元素尺寸其實是直接把桌機版的尺寸除以二。不只元素真滴有夠小,也因為行動版與桌機版大小差距過大,調整平板這類中間螢幕尺寸的 RWD 設定,變得異常耗時。
字太小 難以閱讀 meme

JavaScript:鍵盤操作、行為跟上畫面

1. 鍵盤也要能用

只有滑鼠才觸發的效果,鍵盤使用者看不到。例如縮圖「滑鼠移上去(mouseover)就顯示大圖、移開(mouseout)就隱藏」,鍵盤使用者沒有滑鼠可以移,就永遠看不到大圖。需要「搭配對應的鍵盤事件」。滑鼠移入/移出,對應的是聚焦(focus)和失焦(blur),例如:

thumb.addEventListener('mouseover', showImg)
thumb.addEventListener('mouseout', hideImg)

// 鍵盤版:Tab 聚焦時顯示、離開時隱藏
thumb.addEventListener('focus', showImg)
thumb.addEventListener('blur', hideImg)

💡 前提是這個元素本身要能被聚焦,像是 <a>、<button>,或加了 tabindex="0",否則 focus 事件也不會發生。

同理,用 <div> 做的「按鈕」,click 事件對於 <div tabindex="0"> 不會被 Enter 觸發,要自己補程式碼。或者改用 <button>。

2. 自製元件加上 ARIA

如果遇到需要自製 Tabs、Dialog、Combobox 之類,至少需要可聚焦(tabindex="0")、有 role、有鍵盤操作(Enter/Space、方向鍵、Esc)。元件的鍵盤互動規範,可以直接查 W3C 的 ARIA Authoring Practices Guide(APG)。

3. 焦點管理

  • 開啟 Modal 後:焦點要移進去、Tab 不能跑到後面的頁面、Esc 可以關閉、關閉後焦點要回到觸發它的按鈕。優先使用原生的 <dialog> 搭配 showModal(),因為瀏覽器已經幫你處理了大部分的事,真香!
const dialog = document.querySelector('dialog')
openBtn.addEventListener('click', () => dialog.showModal())
  • SPA 換頁要更新頁面標題和焦點,不然螢幕閱讀器不會知道已經換頁了。

4. 動態更新要被「說出來」

畫面上出現錯誤訊息、Toast,對看得到畫面的人是一目了然,但螢幕閱讀器不會自己知道。要用 live region 通知它:

<!-- 一般通知 -->
<div role="status" aria-live="polite">{{ message }}</div>

<!-- 緊急錯誤 -->
<div role="alert">{{ errorMessage }}</div>

要注意,live region 的容器要在頁面載入時就存在,再去更新裡面的內容,才會被朗讀。載入中的動畫也一樣,除了動畫之外,給一個文字狀態(或 aria-busy)。至於表單驗證,MDN 的建議是送出時才驗證,不要每打一個字就跳錯誤;錯誤摘要放在前面,並讓每一條都連到對應的欄位。

其他還有:

  • 拖曳要有替代操作:前面做的拖曳和排序,都可以提供鍵盤或點選的方式完成同一件事。例如排序的話加「上移/下移」按鈕,配對的話改成「選取項目後再選目標」。

  • 漸進增強:MDN 建議先用純 HTML 把基本功能做出來,再用 JavaScript 往上加強,而不是整個頁面都靠 JS 才長得出來。在 SPA 專案中可以理解成,能用原生元素就不要自己用 JS 重做,像是表單用 <form> 和 <button type="submit">、連結用 <a href>,這樣即使 JS 出了狀況,基本行為還是由瀏覽器負責。


本來覺得無障礙網頁做起來很麻煩,透過這次寫文章才發現,依循瀏覽器原生行為、HTML 標籤正確使用,就算是打好基本盤。再來自製元件加上 ARIA、動畫支援 prefers-reduced-motion 等設定,也算是完成大部分了。剩下就是檢查是否能單靠鍵盤操作主要流程。如此一來,就能把「好看」補成「好用」,讓專案更上一層樓。

.
.
.
Day29 Clear!倒數剩下 1 天! キタ━━━(゜∀゜)━━━!!!!!


參考資料


上一篇
【Day 28】動畫實作:Lottie 動畫優化,從手寫到元件封裝
下一篇
# 【Day 30】完賽心得:從不寫文章,到每天發文
系列文
從實務需求出發:前端視覺與 UI 互動開發實踐 共 30 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言