本教學課程說明如何建構網站的無障礙主要導覽。您將瞭解語意 HTML、無障礙功能,以及使用 ARIA 屬性有時可能弊大於利。
在樣式、功能、基礎標記和語意資訊方面,建構網站主要導覽的方式有很多種。如果實作方式過於精簡,雖然適用於大多數人,但使用者體驗 (UX) 可能不佳。如果設計過於複雜,可能會讓使用者感到困惑,甚至無法存取。
對大多數網站來說,您需要建構的網站既不能太簡單,也不能太複雜。
逐層建構
在本教學課程中,您將從基本設定開始,逐層新增功能,直到提供足夠的資訊、樣式和功能,滿足大多數使用者的需求。為此,您可以使用漸進增強原則,從最基本且穩固的解決方案開始,逐步新增功能層。如果某個圖層因故無法運作,導覽功能仍可正常運作,因為系統會順利切換至基礎圖層。
基本結構
如要建立基本導覽,您需要兩項元素:<a> 元素和幾行 CSS,以改善連結的預設樣式和版面配置。
HTML
<a href="/home">Home</a> <a href="/about-us">About us</a> <a href="/pricing">Pricing</a> <a href="/contact">Contact</a>CSS
/* Define variables for your colors */ :root { --color-shades-dark: rgb(25, 25, 25); } /* Use the alternative box model Details: <https://web.dev/learn/css/box-model/> */ *{ box-sizing: border-box; } /* Basic font styling */ body { font-family: Segoe UI, system-ui, -apple-system, sans-serif; font-size: 1.6rem; } /* Link styling */ a { --text-color: var(--color-shades-dark); border-block-end: 3px solid var(--border-color, transparent); color: var(--text-color); display: inline-block; margin-block-end: 0.5rem; /* See note at the bottom of this chapter */ margin-inline-end: 0.5rem; padding: 0.1rem; text-decoration: none; } /* Change the border-color on :hover and :focus */ a:where(:hover, :focus) { --border-color: var(--text-color); }
無論使用者如何存取網站,這種做法對大多數使用者都適用。導覽功能可透過滑鼠、鍵盤、觸控裝置或螢幕閱讀器存取,但仍有改進空間。您可以擴充這個基本模式,加入更多功能和資訊,提升使用者體驗。
您可以採取下列步驟:
- 醒目顯示目前頁面。
- 向螢幕閱讀器使用者朗讀項目數量。
- 新增地標,讓螢幕閱讀器使用者能直接使用快速鍵瀏覽。
- 在窄可視區域中隱藏導覽。
- 改善焦點樣式。
醒目顯示使用中的頁面
如要醒目顯示目前頁面,可以為對應的連結新增類別。
<a href="/about-us" class="active-page">About us</a>
這種做法的問題在於,它只透過視覺方式傳達哪個連結處於啟用狀態。失明的螢幕閱讀器使用者無法分辨目前頁面和其他頁面。幸好,無障礙網頁應用程式 (ARIA) 標準也提供一種以語意方式傳達這項資訊的方法。請使用 aria-current="page" 屬性和值,而非類別。
aria-current (狀態) 表示容器或一組相關元素中代表目前項目的元素。
用於表示一組分頁連結中某個連結的網頁權杖,該連結的視覺樣式代表目前顯示的網頁。
[Accessible Rich Internet Applications (WAI-ARIA) 1.1](https://www.w3.org/TR/wai-aria/#aria-current)
有了這個額外屬性,螢幕閱讀器現在會宣告「目前頁面、連結、關於我們」,而不是只宣告「連結、關於我們」。
<a href="/about-us" aria-current="page" class="active-page">About us</a>
這項做法的好處是,您可以使用屬性選取 CSS 中的有效連結,因此不需要 active-page 類別。
<a href="/home">Home</a>
<a href="/about-us" aria-current="page">About us</a>
<a href="/pricing">Pricing</a>
<a href="/contact">Contact</a>
/* Change border-color and color for the active page */
[aria-current="page"] {
--border-color: var(--color-highlight);
--text-color: var(--color-highlight);
}
朗讀項目數量
視力正常的使用者只要查看導覽,就能知道其中只有四個連結。視障螢幕閱讀器使用者無法快速取得這項資訊。他們可能需要逐一查看整個連結清單。如果清單很短 (如本例所示),這可能不是問題,但如果清單包含 40 個連結,這項工作可能會很繁瑣。如果螢幕閱讀器使用者事先知道導覽區塊包含許多連結,可能會決定改用其他更有效率的導覽方式,例如站內搜尋。
如要預先告知項目數量,建議將每個連結包裝在清單項目 (<li>) 中,並嵌套在未排序的清單 (<ul>) 中。
<ul>
<li>
<a href="/home">Home</a>
</li>
<li>
<a href="/about-us" aria-current="page">About us</a>
</li>
<li>
<a href="/pricing">Pricing</a>
</li>
<li>
<a href="/contact">Contact</a>
</li>
</ul>
當螢幕閱讀器使用者找到清單時,軟體會朗讀類似「清單,4 個項目」的內容。
以下示範如何在 Windows 上搭配螢幕閱讀器 NVDA 瀏覽。
現在,您必須調整樣式,讓外觀看起來與先前相同。
/* Remove the default list styling and create a flexible layout for the list */
ul {
display: flex;
flex-wrap: wrap;
gap: 1rem;
list-style: none;
margin: 0;
padding: 0;
}
/* Basic link styling */
a {
--text-color: var(--color-shades-dark);
border-block-end: 3px solid var(--border-color, transparent);
color: var(--text-color);
padding: 0.1rem;
text-decoration: none;
}
使用清單對螢幕閱讀器使用者有許多好處:
- 他們可以在與項目互動前,先取得項目總數。
- 他們可能會使用快速鍵在清單項目之間跳轉。
- 他們可能會使用快速鍵在清單之間跳轉。
- 螢幕閱讀器可能會朗讀目前項目的索引 (例如「清單項目,四分之二」)。
此外,如果網頁不含 CSS,清單會將連結顯示為一組連貫的項目,而不是一堆連結。
Safari 的「旁白」功能有一點值得注意:設定 list-style: none 後,您會失去所有這些優勢。這是設計所致。WebKit 團隊決定在清單看起來不像清單時,移除清單語意。視導覽的複雜程度而定,這可能不是問題。一方面,導覽功能仍可使用,且只會影響 Safari 中的「旁白」功能。VoiceOver 搭配 Chrome 或 Firefox 仍會朗讀項目數量,其他螢幕閱讀器 (例如 NVDA) 也是如此。另一方面,語意資訊在某些情況下可能非常實用。如要做出這項決定,請邀請實際使用螢幕閱讀器的使用者測試導覽功能,並取得他們的意見回饋。如果您決定在 Safari 中使用 VoiceOver,且希望其行為與所有其他螢幕閱讀器相同,可以明確設定 <ul> 的 ARIA 清單角色,藉此解決問題。這會將行為還原至移除清單樣式前的狀態。清單外觀仍維持不變。
<ul role="list">
<li>
<a href="/home">Home</a>
</li>
...
</ul>
新增地標
你只花了少許心力,就大幅改善螢幕閱讀器使用者的體驗,但還有一件事可以做。在語意上,導覽列仍只是一份連結清單,很難看出這份特定清單是網站的主要導覽列。只要將 <ul> 包裝在 <nav> 元素中,即可將這個一般清單轉換為導覽清單。
使用 <nav> 元素有幾項優點。值得注意的是,當使用者與該元素互動時,螢幕閱讀器會朗讀「導覽」之類的內容,並在網頁中加入地標。地標是頁面上的特殊區域,例如 <header>、<footer> 或 <main>,螢幕閱讀器可以跳到這些區域。在網頁上設定地標相當實用,因為螢幕閱讀器使用者可以直接存取網頁上的重要區域,不必與網頁的其他部分互動。舉例來說,在 NVDA 中按下 D 鍵,即可從一個位置標記跳到下一個。在 VoiceOver 中,你可以按下 VO + U 鍵,使用轉輪列出頁面上的所有地標。
這份清單會顯示 4 個地標:橫幅是 <header> 元素、導覽是 <nav>、主要是 <main> 元素,而內容資訊則是 <footer>。這份清單不應太長,你只需要將 UI 的重要部分標示為位置標記,例如站內搜尋、本機導覽或分頁。
如果網站有整個網站導覽、網頁有本機導覽,且單一網頁有分頁,則可能會有 3 個 <nav> 元素。這沒問題,但現在有三個導覽地標,而且語意上看起來都一樣。除非你非常熟悉頁面結構,否則很難分辨兩者。
為區分這些元素,請使用 aria-labelledby 或 aria-label 為其加上標籤。
<nav aria-label="Main">
<ul>
<li>
<a href="/home">Home</a>
</li>
...
</ul>
</nav>
...
<nav aria-label="Select page">
<ul>
<li>
<a href="/page-1">1</a>
</li>
...
</ul>
</nav>
如果您選取的標籤已存在於頁面中,則可改用 aria-labelledby,並使用 id 屬性參照現有標籤。
<nav aria-labelledby="pagination_heading">
<h2 id="pagination_heading">Select a page</h2>
<ul>
<li>
<a href="/page-1">1</a>
</li>
...
</ul>
</nav>
標籤應簡潔扼要,避免過於冗長。請省略「導覽」或「選單」等字詞,因為螢幕閱讀器會向使用者提供這類資訊。
在窄可視區域中隱藏導覽
我個人不喜歡在窄檢視區中隱藏主要導覽,但如果連結清單太長,就無法避免。在這種情況下,使用者會看到標示為「選單」的按鈕、漢堡圖示或兩者組合,而非清單。點選按鈕即可顯示或隱藏清單。如果您具備 JavaScript 和 CSS 的基本知識,這項工作並不難,但您必須注意使用者體驗和無障礙功能方面的幾項要點。
- 您必須以無障礙方式隱藏清單。
- 導覽功能必須可透過鍵盤操作。
- 導覽功能必須傳達是否可見。
新增漢堡按鈕
由於您遵循漸進增強原則,因此必須確保即使關閉 JavaScript,導覽功能仍可正常運作且合理。
導覽功能首先需要的是漢堡按鈕。您可以在範本元素中以 HTML 建立,在 JavaScript 中複製,然後新增至導覽。
<nav id="mainnav">
...
</nav>
<template id="burger-template">
<button type="button" aria-expanded="false" aria-label="Menu" aria-controls="mainnav">
<svg width="24" height="24" aria-hidden="true">
<path d="M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z">
</svg>
</button>
</template>
aria-expanded屬性會告知螢幕閱讀器軟體,按鈕控制的元素是否已展開。aria-label會為按鈕提供所謂的無障礙元素名稱,也就是漢堡圖示的替代文字。- 您可以使用
aria-hidden隱藏輔助技術中的<svg>,因為aria-label已提供文字標籤。 aria-controls會告知支援該屬性的輔助技術 (例如 JAWS),按鈕控制的是哪個元素。
const nav = document.querySelector('#mainnav')
const list = nav.querySelector('ul');
const burgerClone = document.querySelector('#burger-template').content.cloneNode(true);
const button = burgerClone.querySelector('button');
// Toggle aria-expanded attribute
button.addEventListener('click', e => {
// aria-expanded="true" signals that the menu is currently open
const isOpen = button.getAttribute('aria-expanded') === "true"
button.setAttribute('aria-expanded', !isOpen);
});
// Hide list on keydown Escape
nav.addEventListener('keyup', e => {
if (e.code === 'Escape') {
button.setAttribute('aria-expanded', false);
}
});
// Add the button to the page
nav.insertBefore(burgerClone, list);
- 使用者可以隨時關閉導覽,例如按下 Escape 鍵,這對使用者來說很方便。
- 請務必使用
insertBefore,而非appendChild,因為按鈕應是導覽中的第一個元素。如果鍵盤或螢幕閱讀器使用者在點選按鈕後按下 Tab 鍵,他們會預期焦點移至清單中的第一個項目。如果按鈕位於清單之後,就不會發生這種情況。
接著,您要重設按鈕的預設樣式,並確保按鈕只會顯示在窄檢視區中。
@media (min-width: 48em) {
nav {
--nav-button-display: none;
}
}
/* Reset button styling */
button {
all: unset;
display: var(--nav-button-display, flex);
}
隱藏清單
隱藏清單前,請先調整導覽和清單的位置和樣式,讓版面配置適合窄檢視區,但在較大螢幕上仍能正常顯示。
首先,將 <nav> 從頁面的自然流程中移除,並將其置於可視區域的頂端角落。
@media (min-width: 48em) {
nav {
--nav-button-display: none;
--nav-position: static;
}
}
nav {
position: var(--nav-position, fixed);
inset-block-start: 1rem;
inset-inline-end: 1rem;
}
接著,新增自訂屬性 (—-nav-list-layout),變更窄檢視區的版面配置。版面配置預設為欄,在較大的螢幕上會切換為列。
@media (min-width: 48em) {
nav {
--nav-button-display: none;
--nav-position: static;
}
ul {
--nav-list-layout: row;
}
}
ul {
display: flex;
flex-direction: var(--nav-list-layout, column);
flex-wrap: wrap;
gap: 1rem;
list-style: none;
margin: 0;
padding: 0;
}
在窄檢視區中,導覽列應如下所示。
這份清單顯然需要一些 CSS。我們會將其移至頂端角落,讓它垂直填滿整個螢幕,並套用 background-color 和 box-shadow。
@media (min-width: 48em) {
nav {
--nav-button-display: none;
--nav-position: static;
}
ul {
--nav-list-layout: row;
--nav-list-position: static;
--nav-list-padding: 0;
--nav-list-height: auto;
--nav-list-width: 100%;
--nav-list-shadow: none;
}
}
ul {
background: rgb(255, 255, 255);
box-shadow: var(--nav-list-shadow, -5px 0 11px 0 rgb(0 0 0 / 0.2));
display: flex;
flex-direction: var(--nav-list-layout, column);
flex-wrap: wrap;
gap: 1rem;
height: var(--nav-list-height, 100vh);
list-style: none;
margin: 0;
padding: var(--nav-list-padding, 2rem);
position: var(--nav-list-position, fixed);
inset-block-start: 0; /* Logical property. Equivalent to top: 0; */
inset-inline-end: 0; /* Logical property. Equivalent to right: 0; */
width: var(--nav-list-width, min(22rem, 100vw));
}
button {
all: unset;
display: var(--nav-button-display, flex);
position: relative;
z-index: 1;
}
在窄檢視區中,清單看起來應該像這樣,更像是側欄,而不是簡單的清單。
最後,隱藏清單,只在使用者點選按鈕一次時顯示,再次點選時隱藏。請務必只隱藏清單,而非整個導覽,因為隱藏導覽也代表隱藏重要地標。
先前您已在按鈕中加入點擊事件,用於切換 aria-expanded 屬性的值。您可以使用這項資訊做為在 CSS 中顯示及隱藏清單的條件。
@media (min-width: 48em) {
ul {
--nav-list-visibility: visible;
}
}
ul {
visibility: var(--nav-list-visibility, visible);
}
/* Hide the list on narrow viewports, if it comes after an element with
aria-expanded set to "false". */
[aria-expanded="false"] + ul {
visibility: var(--nav-list-visibility, hidden);
}
請務必使用 visibility: hidden 或 display: none 等屬性宣告,而非 opacity: 0 或 translateX(100%) 來隱藏清單。這些屬性可確保在隱藏導覽時,連結無法聚焦。使用 opacity 或 translate 會移除內容的視覺效果,因此連結會隱藏,但仍可使用鍵盤存取,這會造成混淆和困擾。使用 visibility 或 display 會隱藏元素,使其無法存取,因此所有使用者都無法看到。
建立清單動畫
如果您想知道為什麼要使用 visibility: hidden; 而不是 display: none;,那是因為您可以為瀏覽權限建立動畫。雖然只有 hidden 和 visible 兩種狀態,但您可以結合 transform 或 opacity 等其他屬性,建立滑入或淡入效果。這不適用於 display: none,因為 display 屬性無法建立動畫。
下列 CSS 轉換 opacity 可建立淡入和淡出效果。
ul {
transition: opacity 0.6s linear, visibility 0.3s linear;
visibility: var(--nav-list-visibility, visible);
}
[aria-expanded="false"] + ul {
opacity: 0;
visibility: var(--nav-list-visibility, hidden);
}
如果您想為動作加上動畫效果,建議您將 transition 屬性納入 prefers-reduced-motion 媒體查詢,因為動畫可能會導致部分使用者噁心、頭暈和頭痛。
ul {
visibility: var(--nav-list-visibility, visible);
}
@media (prefers-reduced-motion: no-preference) {
ul {
transition: transform 0.6s cubic-bezier(.68,-0.55,.27,1.55), visibility 0.3s linear;
}
}
[aria-expanded="false"] + ul {
transform: var(--nav-list-transform, translateX(100%));
visibility: var(--nav-list-visibility, hidden);
}
這樣一來,只有未偏好減少動態效果的使用者才會看到動畫。
改善焦點樣式
鍵盤使用者會根據元素的焦點樣式,在網頁上定位及瀏覽。預設焦點樣式比沒有焦點樣式好 (如果設定 outline: none,就會發生這種情況),但如果能有更清楚可見的自訂焦點樣式,就能提升使用者體驗。
以下是 Chrome 103 中連結的預設焦點樣式。
您可以提供自訂樣式和顏色,改善這項問題。使用 :focus-visible 而非 :focus,可讓瀏覽器決定何時適合顯示焦點樣式。:focus 樣式會向所有人顯示,包括滑鼠、鍵盤和觸控使用者,無論他們是否需要。使用 :focus-visible 時,瀏覽器會根據內部經驗法則,決定是否只向鍵盤使用者顯示,或向所有人顯示。
/* Remove the default :focus outline */
*:focus {
outline: none;
}
/* Show a custom outline on :focus-visible */
*:focus-visible {
outline: 2px solid var(--color-shades-dark);
outline-offset: 4px;
}
:focus-visible 的瀏覽器支援
醒目顯示焦點項目的方式有很多種。建議使用 outline 屬性,因為它不會破壞版面配置 (border 可能會),而且能與 Windows 上的高對比模式搭配使用。background-color 或 box-shadow 屬性不適合,因為自訂對比度設定可能完全不會顯示這些屬性。
恭喜!您已建構出漸進增強、語意豐富、無障礙且適合行動裝置的主要導覽。
總有可以改進的地方,例如:
本文一開始就提到,解決方案應該「既不簡單,也不複雜」,這正是我們目前的狀態。不過,導覽功能可能會過度設計。
導覽與選單
導覽和選單有明顯差異。導覽是一組連結,用於瀏覽相關文件。選單是可在文件中執行的動作集合。有時這些工作會重疊。導覽列可能包含執行動作的按鈕,例如開啟互動視窗,或選單中可能包含前往其他頁面 (例如說明頁面) 的動作。在這種情況下,請務必不要混用 ARIA 角色,而是找出元件的主要用途,並據此選擇標記和角色。
<nav> 元素具有隱含的 ARIA 導覽角色,足以傳達該元素是導覽,但您通常會看到網站也使用選單、選單列和選單項目。由於我們有時會交替使用這些字詞,因此可能會認為將兩者結合,有助於改善螢幕閱讀器使用者的體驗。在瞭解為何通常不是這樣之前,我們先來看看這些角色的官方定義。
導覽角色
用於瀏覽文件或相關文件的導覽元素 (通常是連結) 集合。
navigation (role) WAI-ARIA 1.1
選單角色
選單通常是使用者可叫用的常見動作或函式清單。如果選單項目清單的呈現方式類似電腦應用程式的選單,則適合使用選單角色。
menu (role) WAI-ARIA 1.1
選單列角色
通常會持續顯示且通常會水平顯示的選單。menubar 角色可用來建立選單列,類似於 Windows、Mac 和 Gnome 桌面應用程式中的選單列。選單列用於建立一組一致的常用指令。作者應確保選單列互動與電腦圖形使用者介面中的一般選單列互動類似。
menubar (role) WAI-ARIA 1.1
menuitem 角色
menuitem (role) WAI-ARIA 1.1
規格在此非常明確,請使用導覽功能瀏覽文件或相關文件,並僅針對動作或功能清單使用選單,類似於電腦應用程式中的選單。如果您不是要建構下一個 Google 文件,可能就不需要為主要導覽列設定任何選單角色。
何時適合使用選單?
選單項目的主要用途是執行動作,而非導覽。假設您有一份資料清單或表格,使用者可以對清單中的每個項目執行特定動作。您可以在每個資料列中加入按鈕,並在使用者點選按鈕時顯示動作。
<ul>
<li>
Product 1
<button aria-expanded="false" aria-controls="options1">Edit</button>
<div role="menu" id="options1">
<button role="menuitem">
Duplicate
</button>
<button role="menuitem">
Delete
</button>
<button role="menuitem">
Disable
</button>
</div>
</li>
<li>
Product 2
...
</li>
</ul>
使用選單角色的影響
請務必謹慎使用這些選單角色,因為很容易出錯。
選單需要特定的 DOM 結構。menuitem 必須是 menu 的直接子項項目。下列程式碼可能會破壞語意行為:
<!-- Wrong, don't do this -->
<ul role="menu">
<li>
<a href="#" role="menuitem">Item 1</a>
</li>
</ul>
熟悉操作的使用者會預期某些鍵盤快速鍵能與選單和選單列搭配使用。根據 ARIA 編寫實務指南 (APG),這包括:
- Enter 鍵和空格鍵:選取選單項目。
- 使用各方向的方向鍵,即可在項目之間移動。
- Home 和 End 鍵:分別將焦點移至第一個或最後一個項目。
- a-z:將焦點移至下一個選單項目,其標籤開頭為輸入的字元。
- 按 Esc 鍵關閉選單。
如果螢幕閱讀器偵測到選單,軟體可能會自動變更瀏覽模式,以便使用上述快速鍵。不熟悉螢幕閱讀器的使用者可能不知道這些快速鍵或使用方式,因此無法使用選單。
鍵盤使用者也一樣,他們可能預期可以使用 Shift 和 Shift + Tab。
建立選單和選單列時,需要考量許多因素,首先要考慮的是是否適合使用。建構一般網站時,只需要包含清單和連結的 nav 元素。這也包括單頁應用程式 (SPA) 或網頁應用程式。基礎堆疊並不重要。除非您要建構與電腦應用程式非常相似的內容,否則請避免使用選單角色。
其他資源
- Scott O'hara 的「Fixing Lists」。
- Adrian Roselli 的「Don't Use ARIA Menu Roles for Site Nav」(請勿將 ARIA 選單角色用於網站導覽)。
- Heydon Pickering 的選單和選單按鈕。
- WAI-ARIA 選單,以及為何應謹慎處理,作者:Marco Zehe。
- Kitty Giraudel 的「Hiding content responsibly」(以負責任的方式隱藏內容)。
- Matthias Ott 的「:focus-visible Is Here」。