昨天終於完成了第一顆自己的 Button,也定義了 variant、size 和 data-cui-* Component Contract。
今天先不急著做第二顆元件。
先把滑鼠放下來,看看昨天做的 Button 還能不能用。😂
看起來像 Button,就真的是 Button 嗎?
平常做網頁時,要做出一個「可以點的東西」其實很容易:
<div
className="rounded-lg bg-primary px-4 py-2 text-primary-foreground"
onClick={() => alert("儲存")}
>
儲存
</div>
用滑鼠點,完全沒問題。
但如果把滑鼠放下,開始按 Tab:
……它直接被跳過了。
因為它本質上仍然只是一個 。
換成真正的 Button:
<Button onClick={() => alert("儲存")}>
儲存
</Button>
瀏覽器原本就知道這是一個可以操作的控制項,也已經提供許多基本的鍵盤行為。
這也是為什麼這套 UI Kit 沒有從 自己刻一顆 Button,而是建立在 Button primitive 上。
接著在 Preview 放幾顆 Button:
<div className="flex gap-3">
<Button>儲存</Button>
<Button variant="outline">取消</Button>
<Button disabled>無法使用</Button>
</div>
然後真的把滑鼠放開,只使用鍵盤操作。
可以依序試:
Tab
→ 能不能移動到 Button?
Shift + Tab
→ 能不能往回移動?
Enter
→ 能不能觸發 Button?
Space
→ 能不能觸發 Button?
Disabled
→ 會不會被操作?
這些看起來很基本,但也是元件庫很重要的一部分。
因為如果每個系統都自己實作 Button,就很容易有人漏掉其中一項。
用 Tab 移動到 Button 時,還需要知道「現在在哪裡」。
目前 Button 有:
focus-visible:border-ring
focus-visible:ring-3
focus-visible:ring-ring/50
而 ring 又對應到前面建立的 Focus Token。
所以整個關係會是:
Keyboard Focus
↓
:focus-visible
↓
ring
↓
--cui-color-focus
這也是為什麼不應該只是看到瀏覽器預設外框不好看,就直接:
outline: none;
然後什麼都不補。
對只使用鍵盤的人來說,focus 樣式就是「游標現在在哪裡」。
只有 Icon 的 Button 呢?
另一個常見情況是只有圖示:
<Button size="icon-md">
🔍
</Button>
我們看得懂 🔍 是搜尋,但不能假設所有使用者都能從圖示理解用途。
因此 Icon-only Button 要提供 accessible name:
<Button
size="icon-md"
aria-label="搜尋"
>
🔍
</Button>
視覺上還是只有:
[ 🔍 ]
但輔助科技可以知道這是一個「搜尋」按鈕。
這也會成為 CUI Button 的使用規則之一。
做完這些測試後,我開始比較能理解一件事:
無障礙元件並不是「加幾個 aria-* 就完成」。
一顆 Button 至少同時包含:
Semantic → 它真的是 Button 嗎?
Keyboard → 不用滑鼠能操作嗎?
Focus → 知道現在操作到哪裡嗎?
State → Disabled 等狀態清楚嗎?
Accessible Name → 輔助科技知道它是做什麼的嗎?
Visual → 顏色、對比、尺寸是否合適?
而 UI Kit 的價值,就是盡可能把這些規則集中處理,而不是讓每個開發者每做一顆 Button 都重新想一次。
Day 7 定義了:
這是一顆 CUI Button。
Day 8 則開始確認:
這是一顆真的能被不同使用方式操作的 CUI Button。
目前先替 Button 定下幾個基本原則:
✓ 使用正確的 Button 語意
✓ 支援鍵盤操作
✓ 保留清楚的 focus-visible
✓ Disabled 不可操作
✓ Icon-only Button 必須有 accessible name
接下來隨著元件越來越多,這些規則也會慢慢變成整套 Accessible UI Kit 的共同基礎。