iT邦幫忙

2026 iThome 鐵人賽

DAY 14
0
自我挑戰組

30 天打造 Accessible UI Kit:從 shadcn/ui 到自己的 Design System系列 第 14 篇

Day 14: Checkbox、Radio、Switch:都是選擇,但意思不一樣

  • 分享至 

  • xImage
  •  

前幾天完成了:

Day 11 → Input
Day 12 → Field
Day 13 → Textarea

今天開始處理另一類表單元件:

Checkbox
Radio
Switch

它們看起來都在做:

「選」或「不選」

但實際上三者代表的操作語意並不一樣。

Checkbox → 可以選,也可以不選;多個選項可以同時成立
Radio    → 一組選項中只能選一個
Switch   → 切換某個功能目前的開啟 / 關閉狀態

選錯元件,即使畫面看起來差不多,使用者理解到的意思卻可能完全不同。

今天就來建立 CUI 第一版 Selection Controls。


Checkbox:獨立的 Yes / No 選擇

最常見的是:

☑ 我同意使用條款

或:

偏好的通知方式

☑ Email
☐ 簡訊
☑ App 通知

每一個選項都是獨立成立的。

因此可以:

Email ✓
簡訊  ✓
App   ✓

三個同時被選取。

這就是 Checkbox 最重要的語意。


加入 Checkbox

先加入元件:

npx shadcn@latest add checkbox

接著延續 CUI 的 Component Contract:

<Checkbox
  data-cui-slot="checkbox"
/>

並確認幾個基本狀態:

Unchecked
Checked
Focus
Disabled
Invalid

使用:

<Checkbox id="terms" />

Checkbox 一定要有名稱

單獨放:

<Checkbox />

視覺上只會看到一個方框。

使用者根本不知道:

我要勾什麼?

因此最基本的結構應該是:

<div className="flex items-center gap-2">
  <Checkbox id="terms" />

  <label htmlFor="terms">
    我同意使用條款
  </label>
</div>

點擊:

我同意使用條款

也應該可以切換 Checkbox。

這跟之前 Input 的:

<label htmlFor="email">

其實是同一個概念。


Radio:一組裡只能選一個

接著是 Radio。

例如:

付款方式

◉ 信用卡
○ ATM
○ 超商付款

和 Checkbox 最大的差別:

Checkbox
☑ A
☑ B
☑ C

Radio
◉ A
○ B
○ C

Radio 是「從一組選項中選一個」。

因此它通常不應該單獨存在,而是:

RadioGroup
├─ Radio A
├─ Radio B
└─ Radio C

加入 Radio Group

npx shadcn@latest add radio-group

使用:

<RadioGroup defaultValue="card">
  <div className="flex items-center gap-2">
    <RadioGroupItem
      value="card"
      id="card"
    />

    <label htmlFor="card">
      信用卡
    </label>
  </div>

  <div className="flex items-center gap-2">
    <RadioGroupItem
      value="atm"
      id="atm"
    />

    <label htmlFor="atm">
      ATM
    </label>
  </div>
</RadioGroup>

選擇 ATM 後,信用卡就會取消。

這是 RadioGroup 本身應該提供的行為,而不是自己用 JavaScript 一個一個取消。


Radio Group 也需要 Group Label

每個 Radio 有自己的 Label:

○ 信用卡
○ ATM
○ 超商付款

但還少了一個問題:

這三個選項到底在選什麼?

所以完整結構應該像:

付款方式

○ 信用卡
○ ATM
○ 超商付款

「付款方式」描述的是整組 Radio,而不是其中某一顆。

使用原生 HTML 時,可以想到:

<fieldset>
  <legend>付款方式</legend>

  ...
</fieldset>

這也是很重要的原則:

不只每一個 Control 要有名稱,一整組相關 Control 也需要有名稱。

之後 CUI 在做更完整的 Radio Field 時,也會把 Group Label 納入考量。


Switch:控制現在的狀態

Switch 看起來也只有兩種狀態:

OFF / ON

但它和 Checkbox 的語意不同。

例如:

深色模式       [ ON ]

Switch 表達的是:

「這個功能現在開著還是關著?」

而 Checkbox 比較像:

「我要不要選擇這個項目?」


加入 Switch

npx shadcn@latest add switch

例如:

<div className="flex items-center gap-2">
  <Switch id="notifications" />

  <label htmlFor="notifications">
    開啟通知
  </label>
</div>

同樣不要只有:

<Switch />

而沒有任何可理解的名稱。


Checkbox 和 Switch 怎麼選?

這兩個最容易混淆。

可以用一個簡單問題判斷:

我是在「選擇一件事情」,還是在「切換一個目前狀態」?

例如:

☑ 我同意服務條款

比較適合 Checkbox。

因為這是表單中的一個選擇。

而:

Email 通知      [ ON ]

比較適合 Switch。

因為這是在控制:

Email Notification = Enabled

Switch 通常代表立即生效

還有一個 UX 上很常見的差異。

Checkbox 常常出現在表單:

☑ 接收活動通知

[ 儲存設定 ]

使用者最後按:

儲存設定

才真正送出。

Switch 則常讓人預期:

通知 [ OFF ]
        ↓
      click
        ↓
通知 [ ON ]

切換後狀態立即改變。

所以如果畫面用了 Switch,卻還要求:

切 Switch
↓
按儲存
↓
才生效

就要特別確認這是不是使用者預期的互動方式。

這不完全是程式問題,而是 Component 的語意會影響 UX 預期。


不要只靠顏色表示 Checked

例如 Checkbox:

Unchecked → 灰色
Checked   → 藍色

如果唯一差異只有:

灰 → 藍

對部分使用者來說可能不夠明確。

Checked State 應該還有清楚的形狀提示:

☐
☑

Radio:

○
◉

Switch:

○────
────●

也就是:

Color 可以強化狀態,但不應該是唯一的狀態資訊。


Focus 仍然不能忘記

Selection Controls 一樣必須可以使用鍵盤操作。

延續 CUI 前面建立的 Focus Style:

"focus-visible:border-ring"
"focus-visible:ring-3"
"focus-visible:ring-ring/50"

測試時不要只用滑鼠。

直接:

Tab
Space
Arrow Keys

實際操作看看。

Checkbox 通常可以:

Tab → Focus
Space → Toggle

Switch:

Tab → Focus
Space → Toggle

Radio Group 則需要特別注意:

Tab
→ 進入 Radio Group

Arrow Keys
→ 在選項間移動

這些 Keyboard Interaction 如果底層元件已經正確提供,我們就不需要自己重新實作。


Disabled State

三種 Control 都可能 Disabled:

<Checkbox disabled />

<RadioGroupItem
  value="card"
  disabled
/>

<Switch disabled />

視覺上可以降低強度,但:

Disabled
≠
只是變成灰色

真正重要的是它不能再被操作。

因此仍然優先使用元件提供的原生 / Primitive Disabled API,而不是只寫:

className="opacity-50"

延續 CUI Component Contract

做到今天,我們可以繼續建立:

data-cui-slot="checkbox"

data-cui-slot="radio-group"

data-cui-slot="radio-group-item"

data-cui-slot="switch"

未來 CUI 的 DOM Contract 就會慢慢形成:

button
input
textarea
checkbox
radio-group
radio-group-item
switch

React 版本與未來的 CDN / Legacy 版本,都可以沿用同樣的命名概念。


做一個 Selection Controls Preview

最後把三種元件放在一起:

Selection Controls

Checkbox
☐ 我同意使用條款
☑ 接收 Email 通知

Radio
付款方式

◉ 信用卡
○ ATM
○ 超商付款

Switch
深色模式       [ OFF ]
Email 通知     [ ON  ]

除了 Default,也可以放:

Checked
Unchecked
Disabled
Focus

幾個代表案例。


實際用鍵盤測一次

今天特別值得做一輪:

1. 不碰滑鼠

2. 按 Tab 移動

3. 找到 Checkbox

4. Space 切換

5. Tab 到 Radio Group

6. 使用方向鍵切換 Radio

7. Tab 到 Switch

8. Space 切換

如果完全不用滑鼠也能完成操作,才代表這些 Control 的基本 Keyboard Interaction 是成立的。


Day 14 Done

今天加入三種 Selection Controls:

✓ Checkbox
✓ Radio Group
✓ Switch

✓ 每個 Control 都有可理解的 Label
✓ Radio Group 本身也需要 Group Label
✓ Checked State 不只靠顏色
✓ Focus State
✓ Disabled State
✓ Keyboard Interaction

✓ Checkbox → 獨立選擇
✓ Radio → 一組選一個
✓ Switch → 開啟 / 關閉目前狀態

三種元件乍看之下都只有:

Yes / No
On / Off
Selected / Unselected

但真正決定該使用哪一個的,不是它們「長什麼樣子」,而是:

這個操作對使用者來說代表什麼?

做到 Day 14,CUI 的 Form Family 也越來越完整:

Form
│
├─ Field
│  ├─ FieldLabel
│  ├─ FieldDescription
│  └─ FieldError
│
├─ Input
├─ Textarea
│
└─ Selection Controls
   ├─ Checkbox
   ├─ Radio Group
   └─ Switch

前面幾天主要處理「輸入」與「選擇」。

下一篇 Day 15,可以開始進入更有互動性的元件:Select——為什麼原生 <select> 很好用,而自訂 Select 又會突然讓無障礙難度上升?


上一篇
Day 13: Textarea:不是把 Input 拉高就結束
下一篇
Day 15: Select:不只是把選項藏進下拉選單
系列文
30 天打造 Accessible UI Kit:從 shadcn/ui 到自己的 Design System 共 19 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言