跳至主要内容

[note] Accessibility, a11y

此篇為各筆記之整理,非原創內容,資料來源可見下方連結與文後參考資料。

Accessibility Standards

Understanding the Web Content Accessibility Guidelines @ MDN

Accessibility 的標準最主要由 Web Content Accessibility Guidelines (WCAG) 加以定義和規範,並根據 conformance level 可以分成三個等級:A, AA, AAA (highest)。

除了 WCAG 之外,Web Accessibility in Mind (WebAIM) 則提供了 checklist 可以在實作時參考。

Screen Readers

透過 Screen readers 可以將文字轉成語音,讓使用者可以聽到網站中的文字內容,並透過鍵盤來瀏覽頁面。

使用 Mac 上的 VoiceOver

Mac 上的 VoiceOver 就是一個相當完整的 Screen reader,只需到 System Preference -> Accessibility -> VoiceOver 即可啟用它。或者也可以使用快捷鍵「CMD + 點擊 3 下 TouchID」:

VoiceOver

Accessible HTML

在學習下述這些屬性是非常建議參考已經實作好 a11y 的框架來搭配學習,例如 bootstrap。

Image

在使用 <img /> 標籤的時候盡可能搭配 alt 屬性,如果不知道要寫什麼,留空白也比不加好:

<!-- screen reader 會閱讀 alt 中的文字 -->
<img src="https://example.com/foo.jpg" alt="An example image" />

<!-- alt 留空字串的話 screen reader 會跳過該圖片 -->
<img src="https://example.com/foo.jpg" alt="" />

<!-- 沒有 alt 的話 screen reader 會惱人的把網址唸出來 -->
<img src="https://example.com/foo.jpg" />

Label

label @ MDN

label 除了可以最常搭配 input 使用外,實際上也可以作用在其他 "labelable" 的元素,例如 <button><select><textarea><meter>、等等...,可以進一步參考 Accessibility concerns @ MDN。

ARIA and roles

Using ARIA: Roles, states, and properties @ MDN

aria-label, aria-labelledby, aria-describedby

對於不能使用 label 但又希望能提供 screen reader label 功能的元素(或者對於沒有文字內容的 button),則可以使用 aria-label,例如:

<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>

aria-labelaria-labelledby 的功能相同,但後者需要對應到某個 id,例如:

<div aria-labelledby="exampleModalLabel" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<!-- aria-labelledby 對應到這裡的 id -->
<h5 class="modal-title" id="exampleModalLabel">Modal title</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">...</div>
<div class="modal-footer">...</div>
</div>
</div>
</div>

aria-labelledbyaria-describedby 的差別在於,describeby 通常是用來放一些延伸說明。

role attribute

瀏覽器預設就已經針對不同 role 設定了不同相關的 accessibility 項目,所以如果我們真的需要把某些元素套用不同預設的 a11y 時,就可以擅用 role 這個屬性:

<!-- 讓 a 帶有 button 預設的 a11y 效果 -->
<p>
This <a href="#" role="button">button</a> triggers a popover on click.
</p>

visually-hidden, visually-only-focusable

可以參考 Bootstrap visually hidden content 中的 visually-hiddenvisually-hidden-focusable 這兩個 class 的實作。

備註

在 Bootstrap v4 時,這兩個 class 是被稱作 sr-onlysr-only-focusable

有些頁面上的內容會希望從畫面中隱藏,但仍希望 scree readers 可以閱讀相關的資訊時,則可以使用 sr-only,例如用了額外說明顏色表示的訊息:

<!-- https://getbootstrap.com/docs/5.1/getting-started/accessibility/ -->
<p class="text-danger">
<!-- 使用 sr-only 來提示視覺障礙的使用者這是須留意的操作 -->
<span class="visually-hidden">Danger: </span>
This action is not reversible
</p>

除了單純使用 visually-hidden 之外,如果希望某些元素是在 focus 是會顯示,但一般的時候不會顯示的話,則可以使用 visually-hidden-focusable

<a class="visually-hidden-focusable" href="#content">Skip to main content</a>

這兩個 class 的實作是:

.visually-hidden,
.visually-hidden-focusable:not(:focus):not(:focus-within) {
position: absolute!important;
width: 1px!important;
height: 1px!important;
padding: 0!important;
/* 透過 margin: -1px 把 width, height 各為 1px 的元素隱藏 */
margin: -1px!important;
overflow: hidden!important;
clip: rect(0,0,0,0)!important;
white-space: nowrap!important;
border: 0!important;
}

Focus Management

許多網站都會在網頁最上方提供 skip links 的功能,它的目的是讓鍵盤瀏覽網頁的使用者能夠避免要一直點擊 Tab 以跳過上方 header 或左側導覽列,才能進到主要內容。

skip links

要做 Skip link 可以直接參考 Bootstrap 的官方網站,基本上就是使用 visually-hidden-focusable 就能達到。

Keyboard & Tab Navigation

Keyboard @ MDN > WCAG Guidelines

在使用鍵盤瀏覽網站時,最基本的來說,使用 「Tab」 可以到下一個 tabbable item,使用 「Shift + Tab」 則可以到前一個 tabbable item。

但對於有選項後可以被展開的項目來說,後續則是使用「上下左右」鍵來操作:

dropdown with arrow

對於任何可以被鍵盤所 focused 的元素來說,它都應該要是有互動意義存在的,也就是說,如果這個元素能被 focused,它就應該要有額外可被互動,像是能夠打開其他連結、或展開下拉選單等等。

任何可以被點擊(clickable)或可互動的元素,也都應該要能被鍵盤所 focusable,並透過 keyboard event 讓它帶有互動上的意義。

對於預設不能被 focus 的元素,可以使用 tabindex 這個屬性(參考:TabIndex and Focus styles @ pjchender),但要確保這個 focusable 的元素有其互動上的意義(可能需要額外處理 keyboard event),同時記得要為它添加 :focus 的樣式。

<!-- https://learn-a11y.netlify.app/accessible-html/index.html -->
<div
aria-label="Alert the word hello"
tabindex="0"
role="button"
onclick="alert('hello')"
onkeyup="alert('hello')"
>
Click me!
</div>

Active Element

使用 document.activeElement 可以取得使用者目前 focus 的元素;使用 .focus() 則可以讓特定的「focusable 元素」被 focus:

const currentFocusElement = document.activeElement;

focusableElement.focus();
提示

focusableElement.focus() 只能用在「能被 focus 的元素」上(包含使用了 tabindex="0" 的元素)。

Tab Trapping

Tab Trapping 的技巧常用在 Modal 中,在 Modal 中,使用者在操作鍵盤的時候,並不會 focus 到 Modal 以外的元素(雖然預設是可以的)。實作的方式可以是監控 Modal 中最後一個可被 focus 的元素,當使用者在該元素上點擊 Tab 時,則 focus 回 Modal 中的第一個元素。

如下圖所示,focus 的元素只會維持在 modal 內:

tab trapping

Visual Considerations

Dark Mode: Prefers Color Scheme

告知瀏覽器(User Agent)支援的色系:color-scheme

另外可以在 HTML 的 <meta> 告知瀏覽器這個網站支援了那些 color-scheme,瀏覽器也會借此調整 UA(User Agent)預設的樣式:

  • normal:頁面內的元素沒有針對 color-scheme 進行優化,因此會使用瀏覽器預設的 color-scheme
  • dark:頁面內的元素可以支援 OS 中的 dark mode
  • light:頁面內的元素可以支援 OS 中的 light mode
<!-- 兩個都給表示兩個都支援,但更偏好前者 -->
<meta name="color-scheme" content="dark light">

類似的效果也可以寫在 CSS 的 :root,但由於 CSS 的解析通常需要在 HTML 之後,所以更偏好直接寫在 HTML 的 <meta> 中:

/* 亮/暗色都支援,但更偏好暗色 */
:root {
color-scheme: dark light;
}
提示

一般來說,可以直接把 User Agent(UA)當成瀏覽器的代名詞。

取得使用者系統偏好的主題:window.matchMedia + prefers-color-scheme

最重要的關鍵是使用 window.matchMedia('(prefers-color-scheme: dark)'); 來了解使用者在系統偏好設定中,是選擇的亮色或暗色主題

const defaultTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';

也可以透過 checkbox 讓使用者切換主題:

/** query DOM elements */
const rootElem = document.querySelector('body.main');
const checkboxElem = document.getElementById('input');

/** get theme the user preferred */
const defaultTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
let currentTheme = defaultTheme;

/** toggle the theme */
const handleToggleTheme = (e) => {
if (e.target.checked) {
currentTheme = 'dark'
} else {
currentTheme = 'light'
}

rootElem.setAttribute('data-theme', currentTheme);
}

const init = () => {
checkboxElem.checked = currentTheme === 'dark';
checkboxElem.addEventListener('change', handleToggleTheme);
rootElem.setAttribute('data-theme', currentTheme);
}

init()

搭配 data-theme 和 CSS variable:

:root {
--background: white;
--text-primary: black;
--text-secondary: royalblue;
--accent: purple;
}

[data-theme='dark'] {
--background: black;
--text-primary: white;
--text-secondary: grey;
--accent: darkred;
}

.main {
background: var(--background);
color: var(--text-primary);
}

button {
color: var(--text-primary);
background-color: var(--background);
border: 2px var(--text-primary) solid;
float: right;
transition: all .5s;
}

(Opt)設定亮暗主題時要套用的樣式:prefers-color-scheme

除了使用 data-theme 與 CSS variable 來達到切換主題配色的這種作法外,如果希望針對特定的主題樣式直接套用顏色是,可以使用 prefers-color-scheme 來設定亮/暗主題時所套用的樣式:

@media (prefers-color-scheme: dark) {
/* 當使用者在系統中選擇的偏好是 dark mode 會被套用 */
.wrapper {
background: black;
}
}

@media (prefers-color-scheme: light) {
/* 當使用者在系統中選擇的偏好是 light mode 會被套用 */
.wrapper {
background: white;
}
}
備註

過去還有一個屬性是 no-preference 但後來因為沒有任何瀏覽器加以實作它,所以已經從 spec 中移除。

Debug and Devtools

在 Chrome 開發者工具中,使用 CMD + Shift + P 叫出 Command Panel 後,搜尋「render」,在 render 面板中可以找到 "prefers-color-scheme" 的模擬功能:

prefers-color-scheme

其他

除了上述提到的 prefers-color-scheme,目前還可以透過 prefers-reduced-motionprefers-contrast 來針對不同的系統偏好來套用不同的 CSS 樣式。

References