本教學課程說明如何建構網站無障礙的主要導覽選單。您會瞭解語意式 HTML、無障礙功能,以及使用 ARIA 屬性有時帶來的負面影響。
建立網站的主要導覽有許多不同的方式,包括樣式、功能、底層標記和語意資訊。如果實作方式過於簡單,雖然大多數人都能使用,但使用者體驗 (UX) 可能不佳。如果過度設計,可能會讓使用者感到困惑,甚至無法存取。
對於大多數網站,您應該建立的網站不宜過於簡單或過於複雜。
逐層建構
在本教學課程中,您將從基本設定開始,然後逐層新增功能,直到提供足夠的資訊、樣式和功能,滿足大多數使用者的需求為止。為達成這項目標,您必須運用漸進式增強原則,也就是從最基本且可靠的解決方案開始,然後逐步加入各層功能。如果某個層級因故無法運作,導覽功能仍會正常運作,因為它會順利切換回基礎層級。
基本結構
如要使用基本導覽,您需要有 <a>
元素和幾行 CSS,來改善連結的預設樣式和版面配置。
<a href="/home">Home</a>
<a href="/about-us">About us</a>
<a href="/pricing">Pricing</a>
<a href="/contact">Contact</a>
/* 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>
這種做法的問題在於,它只會以視覺方式傳達哪個連結處於活動狀態的資訊。視障螢幕閱讀器使用者無法分辨目前的頁面和其他頁面。幸好,Accessible Rich Internet Applications (ARIA) 標準也提供一種方式,可透過語意傳達這類資訊。請使用 aria-current="page" 屬性和值,不要使用類別。
aria-current
(狀態) 表示代表容器或一組相關元素中目前項目的元素。網頁符記,用來表示一組分頁連結內的連結,其中的連結經過樣式設定,代表目前顯示的網頁。
[無障礙網際網路應用程式 (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,清單會將連結顯示為一組連結,而非一堆連結。
值得一提的是,如果您設定 list-style: none
,就會失去 Safari 中的 VoiceOver 所有優點。這是設計所致。WebKit 團隊決定在清單看起來不像清單時移除清單語意。視導覽的複雜度而定,這可能或不是一個問題。一方面,導覽功能仍可使用,而且只會影響 Safari 中的 VoiceOver。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);
- 使用者可以隨時關閉導覽功能,例如按下 Esc 鍵。
- 請務必使用
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
屬性納入偏好動作媒體查詢中,因為動畫可能會對部分使用者造成噁心、暈眩和頭痛。
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 導覽角色,足以傳達元素是導覽功能,但您經常會看到網站也使用 menu、menubar 和 menuitem。由於我們有時會交替使用這些字詞,建議您結合兩者,改善螢幕閱讀器的使用體驗。在瞭解為何這通常不是事實之前,我們先來看看這些角色的官方定義。
導覽角色
用於瀏覽文件或相關文件的導覽元素集合 (通常是連結)。
navigation (role) WAI-ARIA 1.1
選單角色
選單通常包含使用者可叫用的常用動作或功能清單。如果選單項目清單的顯示方式與電腦版應用程式相似,就適合使用選單角色。
menu (role) WAI-ARIA 1.1
選單列角色
呈現選單時,選單通常會持續顯示,且通常會橫向呈現。 選單列角色可建立與 Windows、Mac 和 Gnome 桌面應用程式類似的選單列。您可以使用選單列建立一組常用的一致指令。作者應確保選單列互動方式與電腦圖形使用者介面中的一般選單列互動方式相似。
選單列 (角色) WAI-ARIA 1.1
選單項目角色
menuitem (角色) 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 鍵,也是如此。
建立選單和選單列時,您需要考量許多因素,首先要考量是否適合使用這類元素。建立一般網站時,您只需要使用包含清單和連結的導覽元素即可。這也包括單頁應用程式 (SPA) 或網頁應用程式。基礎堆疊並不重要。除非您要建構與桌面應用程式非常相似的應用程式,否則請避免使用選單角色。
其他資源
- 請參閱 Scott O'hara 的「Fixing Lists」一文。
- Adrian Roselli 的Don't Use ARIA Menu Roles for Site Nav。
- Heydon Pickering 的「Menus & Menu Buttons」
- WAI-ARIA 菜單,以及為何你該用 Marco Zehe 謹慎處理這些菜單。
- 隱藏內容的負責任做法,作者:Kitty Giraudel。
- Matthias Ott 的:focus-visible Is Here。
主頁橫幅圖片由 Mick Haupt 提供