本教學課程說明如何建構網站無障礙的主要導覽選單。您將瞭解語意 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
(狀態) 表示代表容器或一組相關元素中目前項目的元素。頁面符記,用於在分頁連結組中表示連結,其中連結的視覺樣式代表目前顯示的頁面。[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,清單會將連結顯示為一組連結,而非一堆連結。
值得一提的是,如果您設定 list-style: none
,就會失去 Safari 中的 VoiceOver 所有優點。這是設計所致。WebKit 團隊決定在清單看起來不像清單時移除清單語意。視導覽的複雜度而定,這可能或不是一個問題。一方面,導覽功能仍可使用,而且只會影響 Safari 中的 VoiceOver。搭配 Chrome 或 Firefox 使用的 VoiceOver 仍會朗讀項目數量,以及 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
屬性包裝在 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
屬性,因為 outline
不會破壞版面配置 (border
可能會發生這種情況),且可與 Windows 上的高對比模式搭配使用。background-color
或 box-shadow
不適合用於自訂對比設定,因為這些屬性可能完全不會顯示。

恭喜!您已建立逐步改善、語意豐富、易於存取且適合行動裝置的主導覽。
但仍有改進空間,例如:
如您還記得本文開頭的內容,我們希望解決方案「既不太簡單,也不太複雜」,而這正是我們目前的目標。不過,您也可能會過度設計導覽功能。
導覽選單與選單
導覽和選單之間有明顯差異。導覽是連結的集合,可用於瀏覽相關文件。選單是文件中可執行的動作集合。有時這些工作會重疊。導覽可能會包含執行動作的按鈕,例如開啟模式視窗,或是選單,其中一個動作是前往其他頁面,例如說明頁面。在這種情況下,請務必不要混用 ARIA 角色,而是找出元件的主要用途,並據此選擇標記和角色。
<nav>
元素具有隱含的 ARIA 導覽角色,足以傳達元素是導覽功能,但您經常會看到網站也使用 menu、menubar 和 menuitem。由於我們有時會交替使用這些詞彙,因此結合這兩種方式,或許能改善螢幕閱讀器使用者的體驗。在瞭解為何這通常不是事實之前,我們先來看看這些角色的官方定義。
導覽角色
用於瀏覽文件或相關文件的導覽元素集合 (通常是連結)。
navigation (role) WAI-ARIA 1.1
選單角色
選單通常是使用者可叫用的常用動作或函式清單。如果選單項目清單的呈現方式類似於電腦應用程式中的選單,則適合使用選單 role。
menu (role) WAI-ARIA 1.1
選單列角色
通常會以橫向方式顯示,且會持續顯示在畫面上。選單列角色用於建立類似 Windows、Mac 和 Gnome 桌面應用程式的選單列。您可以使用選單列建立一組常用的一致指令。作者應確保選單列互動方式與電腦圖形使用者介面中的一般選單列互動方式相似。
menubar (role) WAI-ARIA 1.1
menuitem 角色
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」
- Marco Zehe 的WAI-ARIA 選單,以及為何應謹慎處理。
- 隱藏內容的負責任做法,作者:Kitty Giraudel。
- Matthias Ott 的:focus-visible Is Here。
主頁橫幅圖片由 Mick Haupt 提供