基礎總覽:如何建構可適應顏色且符合無障礙設計的工具提示自訂元素。
在這篇文章中,我想分享我對如何建構可適應顏色且無障礙的 <tool-tip>
自訂元素的想法。試用示範並查看來源!
如果比較喜歡看影片,可以觀看這篇貼文的 YouTube 版本:
總覽
工具提示是不具模式、不具阻斷性、不具互動性的疊加層,內含使用者介面的補充資訊。預設為隱藏,當滑鼠懸停在相關聯的元素上或聚焦時,就會顯示。工具提示無法直接選取或互動。工具提示並非標籤或其他重要資訊的替代方案,使用者應可在沒有工具提示的情況下,順利完成工作。

錯誤:依賴工具提示而非標籤
切換提示與工具提示
與許多元件一樣,工具提示的說明各有不同,例如 MDN、WAI ARIA、Sarah Higley 和 Inclusive Components。我喜歡工具提示和切換提示之間的區隔。工具提示應包含非互動式補充資訊,切換提示則可包含互動式和重要資訊。造成這種差異的主要原因是無障礙功能,也就是使用者應如何導覽至彈出式視窗,並存取其中的資訊和按鈕。Toggletip 很快就會變得複雜。
以下是 Designcember 網站的切換提示影片,其中包含可互動的疊加層,使用者可以釘選開啟並探索,然後透過輕觸關閉或 Esc 鍵關閉:
這項 GUI 挑戰採用工具提示路線,幾乎所有作業都以 CSS 完成,以下說明如何建構。
標記
我選擇使用自訂元素 <tool-tip>
。如果不想,作者不必將自訂元素做成網頁元件。瀏覽器會將 <foo-bar>
視為 <div>
。您可以將自訂元素視為特異性較低的類別名稱。完全不需要 JavaScript。
<tool-tip>A tooltip</tool-tip>
這就像是內含一些文字的 div。只要加入 [role="tooltip"]
,我們就能與支援的螢幕閱讀器無障礙樹狀結構建立關聯。
<tool-tip role="tooltip">A tooltip</tool-tip>
現在螢幕閱讀器會將其視為工具提示。請參閱下列範例,瞭解第一個連結元素在樹狀結構中是否有可辨識的工具提示元素,第二個連結元素則否。第二個使用者沒有角色。在樣式部分,我們將改善這個樹狀檢視畫面。
接著,我們需要讓工具提示無法成為焦點。如果螢幕閱讀器無法辨識工具提示角色,就會允許使用者將焦點移至 <tool-tip>
讀取內容,但使用者體驗不需要這樣做。螢幕閱讀器會將內容附加至父項元素,因此不需要焦點即可存取。我們可以使用 inert
,確保使用者不會在分頁流程中意外找到這個工具提示內容:
<tool-tip inert role="tooltip">A tooltip</tool-tip>
接著,我選擇使用屬性做為介面,指定工具提示的位置。根據預設,所有 <tool-tip>
都會採用「頂端」位置,但您可以在元素中加入 tip-position
,自訂位置:
<tool-tip role="tooltip" tip-position="right ">A tooltip</tool-tip>
我傾向使用屬性而非類別來處理這類情況,這樣 <tool-tip>
就無法同時指派多個位置。只能有一個或沒有。
最後,將 <tool-tip>
元素放在要提供工具提示的元素內。我在 <picture>
元素中放置圖片和 <tool-tip>
,與有視覺能力的使用者分享 alt
文字:
<picture>
<img alt="The GUI Challenges skull logo" width="100" src="...">
<tool-tip role="tooltip" tip-position="bottom">
The <b>GUI Challenges</b> skull logo
</tool-tip>
</picture>
我在 <abbr>
元素內放置 <tool-tip>
:
<p>
The <abbr>HTML <tool-tip role="tooltip" tip-position="top">Hyper Text Markup Language</tool-tip></abbr> abbr element.
</p>
無障礙設定
由於我選擇建立工具提示而非切換提示,這個部分就簡單多了。首先,請允許我說明我們期望的使用者體驗:
- 在空間有限或介面雜亂的情況下,隱藏補充訊息。
- 使用者將游標懸停在元素上、將焦點移至元素上,或使用觸控方式與元素互動時,顯示訊息。
- 當懸停、焦點或觸控結束時,再次隱藏訊息。
- 最後,如果使用者指定偏好減少動態效果,請確保減少任何動態效果。
我們的目標是提供隨選補充訊息。使用滑鼠或鍵盤的明眼人可以將游標懸停在訊息上,然後用眼睛閱讀訊息。螢幕閱讀器使用者 (無視覺能力) 可以將焦點移至訊息上,透過工具接收訊息內容。

上一節中,我們介紹了無障礙樹狀結構、工具提示角色和 inert,接下來要測試並驗證使用者體驗,確保工具提示訊息能適當顯示給使用者。測試後,我們無法判斷語音訊息的哪個部分是工具提示。在無障礙樹狀結構中進行偵錯時,也可以看到「top」的連結文字與「Look, tooltips!」一起執行,沒有任何遲疑。螢幕閱讀器不會中斷或將文字識別為工具提示內容。
在 <tool-tip>
中新增僅供螢幕閱讀器使用的虛擬元素,即可為視障使用者新增提示文字。
&::before {
content: "; Has tooltip: ";
clip: rect(1px, 1px, 1px, 1px);
clip-path: inset(50%);
height: 1px;
width: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
}
下方是更新後的無障礙樹狀結構,現在連結文字後方會加上半形分號,並提示工具提示「Has tooltip: 」。
現在,螢幕閱讀器使用者將焦點移至連結時,螢幕閱讀器會說出「頂端」,並稍作停頓,然後宣布「有工具提示:看,工具提示」。這可為螢幕閱讀器使用者提供一些實用的 UX 提示。延遲時間可讓連結文字和工具提示之間有適當的間隔。此外,當系統播報「有工具提示」時,螢幕閱讀器使用者如果之前已聽過工具提示,可以輕鬆取消。這與快速懸停和取消懸停非常相似,因為您已看過補充訊息。這感覺像是良好的 UX 同位。
樣式
<tool-tip>
元素會是代表補充訊息的元素子項,因此我們先從疊加效果的基本要素著手。使用 position absolute
將其從文件流程中移除:
tool-tip {
position: absolute;
z-index: 1;
}
如果父項不是堆疊內容,工具提示會將自身定位在最近的堆疊內容,這並非我們所要的。區塊中新增的選取器可提供協助::has()
:
:has(> tool-tip) {
position: relative;
}
請別太擔心瀏覽器支援問題。首先,請記住這些工具提示是補充資訊,如果無法運作,應該就沒問題。其次,在 JavaScript 區段中,我們會部署指令碼,為不支援 :has()
的瀏覽器填補所需功能。
接著,我們將工具提示設為非互動式,以免工具提示從父項元素竊取指標事件:
tool-tip {
…
pointer-events: none;
user-select: none;
}
接著,使用不透明度隱藏工具提示,這樣我們就能以交叉淡化效果轉場工具提示:
tool-tip {
opacity: 0;
}
:has(> tool-tip):is(:hover, :focus-visible, :active) > tool-tip {
opacity: 1;
}
:is()
和 :has()
會在這裡執行大量工作,讓包含父項元素的 tool-tip
瞭解使用者互動,以便切換子項工具提示的顯示狀態。滑鼠使用者可以懸停,鍵盤和螢幕閱讀器使用者可以聚焦,觸控使用者可以輕觸。
顯示和隱藏疊加層的功能適用於有視力的使用者,現在要為這些使用者新增一些樣式,以便設定主題、定位,以及在泡泡中新增三角形。下列樣式開始使用自訂屬性,以目前為基礎,但也會新增陰影、字體排版和顏色,讓樣式看起來像是浮動工具提示:
tool-tip {
--_p-inline: 1.5ch;
--_p-block: .75ch;
--_triangle-size: 7px;
--_bg: hsl(0 0% 20%);
--_shadow-alpha: 50%;
--_bottom-tip: conic-gradient(from -30deg at bottom, rgba(0,0,0,0), #000 1deg 60deg, rgba(0,0,0,0) 61deg) bottom / 100% 50% no-repeat;
--_top-tip: conic-gradient(from 150deg at top, rgba(0,0,0,0), #000 1deg 60deg, rgba(0,0,0,0) 61deg) top / 100% 50% no-repeat;
--_right-tip: conic-gradient(from -120deg at right, rgba(0,0,0,0), #000 1deg 60deg, rgba(0,0,0,0) 61deg) right / 50% 100% no-repeat;
--_left-tip: conic-gradient(from 60deg at left, rgba(0,0,0,0), #000 1deg 60deg, rgba(0,0,0,0) 61deg) left / 50% 100% no-repeat;
pointer-events: none;
user-select: none;
opacity: 0;
transform: translateX(var(--_x, 0)) translateY(var(--_y, 0));
transition: opacity .2s ease, transform .2s ease;
position: absolute;
z-index: 1;
inline-size: max-content;
max-inline-size: 25ch;
text-align: start;
font-size: 1rem;
font-weight: normal;
line-height: normal;
line-height: initial;
padding: var(--_p-block) var(--_p-inline);
margin: 0;
border-radius: 5px;
background: var(--_bg);
color: CanvasText;
will-change: filter;
filter:
drop-shadow(0 3px 3px hsl(0 0% 0% / var(--_shadow-alpha)))
drop-shadow(0 12px 12px hsl(0 0% 0% / var(--_shadow-alpha)));
}
/* create a stacking context for elements with > tool-tips */
:has(> tool-tip) {
position: relative;
}
/* when those parent elements have focus, hover, etc */
:has(> tool-tip):is(:hover, :focus-visible, :active) > tool-tip {
opacity: 1;
transition-delay: 200ms;
}
/* prepend some prose for screen readers only */
tool-tip::before {
content: "; Has tooltip: ";
clip: rect(1px, 1px, 1px, 1px);
clip-path: inset(50%);
height: 1px;
width: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
}
/* tooltip shape is a pseudo element so we can cast a shadow */
tool-tip::after {
content: "";
background: var(--_bg);
position: absolute;
z-index: -1;
inset: 0;
mask: var(--_tip);
}
/* top tooltip styles */
tool-tip:is(
[tip-position="top"],
[tip-position="block-start"],
:not([tip-position]),
[tip-position="bottom"],
[tip-position="block-end"]
) {
text-align: center;
}
主題調整
工具提示只有幾種顏色可供管理,因為文字顏色是透過系統關鍵字 CanvasText
從網頁繼承而來。此外,由於我們已建立自訂屬性來儲存值,因此只需要更新這些自訂屬性,其餘部分則交由主題處理:
@media (prefers-color-scheme: light) {
tool-tip {
--_bg: white;
--_shadow-alpha: 15%;
}
}
如果是淺色主題,我們會將背景調整為白色,並調整陰影的不透明度,大幅降低陰影強度。
由右向左
為支援從右至左的閱讀模式,自訂屬性會將文件方向的值分別儲存為 -1 或 1。
tool-tip {
--isRTL: -1;
}
tool-tip:dir(rtl) {
--isRTL: 1;
}
這項屬性可用於輔助定位工具提示:
tool-tip[tip-position="top"]) {
--_x: calc(50% * var(--isRTL));
}
以及協助判斷三角形的位置:
tool-tip[tip-position="right"]::after {
--_tip: var(--_left-tip);
}
tool-tip[tip-position="right"]:dir(rtl)::after {
--_tip: var(--_right-tip);
}
最後,也可以用於 translateX()
的邏輯轉換:
--_x: calc(var(--isRTL) * -3px * -1);
工具提示位置
使用 inset-block
或 inset-inline
屬性,以邏輯方式放置工具提示,同時處理實體和邏輯工具提示位置。下列程式碼顯示如何為從左到右和從右到左的方向,設定四個位置的樣式。
靠上對齊和區塊開始對齊
tool-tip:is([tip-position="top"], [tip-position="block-start"], :not([tip-position])) {
inset-inline-start: 50%;
inset-block-end: calc(100% + var(--_p-block) + var(--_triangle-size));
--_x: calc(50% * var(--isRTL));
}
tool-tip:is([tip-position="top"], [tip-position="block-start"], :not([tip-position]))::after {
--_tip: var(--_bottom-tip);
inset-block-end: calc(var(--_triangle-size) * -1);
border-block-end: var(--_triangle-size) solid transparent;
}
靠右對齊和行尾對齊
tool-tip:is([tip-position="right"], [tip-position="inline-end"]) {
inset-inline-start: calc(100% + var(--_p-inline) + var(--_triangle-size));
inset-block-end: 50%;
--_y: 50%;
}
tool-tip:is([tip-position="right"], [tip-position="inline-end"])::after {
--_tip: var(--_left-tip);
inset-inline-start: calc(var(--_triangle-size) * -1);
border-inline-start: var(--_triangle-size) solid transparent;
}
tool-tip:is([tip-position="right"], [tip-position="inline-end"]):dir(rtl)::after {
--_tip: var(--_right-tip);
}
靠下對齊和區塊結尾對齊
tool-tip:is([tip-position="bottom"], [tip-position="block-end"]) {
inset-inline-start: 50%;
inset-block-start: calc(100% + var(--_p-block) + var(--_triangle-size));
--_x: calc(50% * var(--isRTL));
}
tool-tip:is([tip-position="bottom"], [tip-position="block-end"])::after {
--_tip: var(--_top-tip);
inset-block-start: calc(var(--_triangle-size) * -1);
border-block-start: var(--_triangle-size) solid transparent;
}
靠左對齊和行內開頭對齊
tool-tip:is([tip-position="left"], [tip-position="inline-start"]) {
inset-inline-end: calc(100% + var(--_p-inline) + var(--_triangle-size));
inset-block-end: 50%;
--_y: 50%;
}
tool-tip:is([tip-position="left"], [tip-position="inline-start"])::after {
--_tip: var(--_right-tip);
inset-inline-end: calc(var(--_triangle-size) * -1);
border-inline-end: var(--_triangle-size) solid transparent;
}
tool-tip:is([tip-position="left"], [tip-position="inline-start"]):dir(rtl)::after {
--_tip: var(--_left-tip);
}
動畫
到目前為止,我們只切換了工具提示的顯示狀態。在本節中,我們會先為所有使用者製作不透明度動畫,因為這是通常安全的減少動作轉場效果。接著,我們會為轉換位置設定動畫,讓工具提示從父項元素滑出。
安全且有意義的預設轉換
設定工具提示元素的樣式,以轉換不透明度和變形,如下所示:
tool-tip {
opacity: 0;
transform: translateX(var(--_x, 0)) translateY(var(--_y, 0));
transition: opacity .2s ease, transform .2s ease;
}
:has(> tool-tip):is(:hover, :focus-visible, :active) > tool-tip {
opacity: 1;
transition-delay: 200ms;
}
為轉場效果新增動態效果
針對工具提示可顯示的每一側,如果使用者接受動態效果,請稍微調整 translateX 屬性的位置,讓工具提示從以下位置移動一小段距離:
@media (prefers-reduced-motion: no-preference) {
:has(> tool-tip:is([tip-position="top"], [tip-position="block-start"], :not([tip-position]))):not(:hover):not(:focus-visible):not(:active) tool-tip {
--_y: 3px;
}
:has(> tool-tip:is([tip-position="right"], [tip-position="inline-end"])):not(:hover):not(:focus-visible):not(:active) tool-tip {
--_x: -3px;
}
:has(> tool-tip:is([tip-position="bottom"], [tip-position="block-end"])):not(:hover):not(:focus-visible):not(:active) tool-tip {
--_y: -3px;
}
:has(> tool-tip:is([tip-position="left"], [tip-position="inline-start"])):not(:hover):not(:focus-visible):not(:active) tool-tip {
--_x: 3px;
}
}
請注意,這是設定「out」狀態,因為「in」狀態位於 translateX(0)
。
JavaScript
我認為 JavaScript 是選用項目。這是因為使用者不需閱讀任何工具提示,就能在 UI 中完成工作。因此,如果工具提示完全失效,應該不會造成太大問題。這也表示我們可以將工具提示視為逐步強化功能。最終所有瀏覽器都會支援 :has()
,這個指令碼也就能完全消失。
只有在瀏覽器不支援 :has()
時,這個 Polyfill 指令碼才會執行兩項作業。請先檢查是否支援 :has()
:
if (!CSS.supports('selector(:has(*))')) {
// do work
}
接著,找出 <tool-tip>
的父項元素,並為這些元素提供要使用的類別名稱:
if (!CSS.supports('selector(:has(*))')) {
document.querySelectorAll('tool-tip').forEach(tooltip =>
tooltip.parentNode.classList.add('has_tool-tip'))
}
接著,請注入一組使用該類別名稱的樣式,模擬 :has()
選取器,以取得完全相同的行為:
if (!CSS.supports('selector(:has(*))')) {
document.querySelectorAll('tool-tip').forEach(tooltip =>
tooltip.parentNode.classList.add('has_tool-tip'))
let styles = document.createElement('style')
styles.textContent = `
.has_tool-tip {
position: relative;
}
.has_tool-tip:is(:hover, :focus-visible, :active) > tool-tip {
opacity: 1;
transition-delay: 200ms;
}
`
document.head.appendChild(styles)
}
這樣就完成了,現在如果瀏覽器不支援 :has()
,就會顯示工具提示。
結論
現在您已瞭解我的做法,您會怎麼做呢?🙂 我很期待能使用 popup
API 簡化切換提示,並使用頂層避免 z-index 衝突,以及使用 anchor
API 更妥善地在視窗中放置項目。在此之前,我會製作工具提示。
讓我們多元化地運用各種方法,學習在網路上建構內容。
建立試聽版,然後在推特上傳送連結給我,我會將連結加到下方的社群混音區!
社群重混作品
目前沒有任何內容。
資源
- Github 上的原始碼