LESSON-06 · 自主延伸

字型、尺度與主題 token

把「字型、尺度與主題 token」拆成 4 個可獨立完成、可重設、可驗證的自主學習 module。

1. 現在要做什麼

成果:完成可切換且 Light/Dark 都具可讀性與鍵盤焦點的主題頁。

需要的檔案:files/starter/index.html(整合起點,保留跨 module 的最小缺口。)

完成後:完成可切換且 Light/Dark 都具可讀性與鍵盤焦點的主題頁。

0 / 8 個操作已記錄

2. 先備自我檢查

逐項展開;答不出來時先走補救連結,不必硬做。

已完成本章基礎練習並能找到 Starter。

自我檢查:我能否指出本章 Starter 入口與唯一 TODO?

不足時補救:回到本章 README,先完成基礎練習與入口導覽。

能開啟本機 HTML 並讀取至少一種瀏覽器證據。

自我檢查:我能否開啟 Elements、Console 或 Network 並找到本頁?

不足時補救:先開 DevTools,重新整理一次,確認目前文件與第一筆 request。

3. 完整學習目標與 Learning Map

每一個目標只對應一個 module;成功狀態必須能指出證據。

能建立由偏好介面字型、本機中文字型到通用字族的有序 fallback stack。

為什麼重要
訪客不一定安裝相同字型;沒有 fallback 時,文字可能落到不合適的預設字型,影響可讀性與版面寬度。
完成條件
在不載入外部字型的本機 fixture 中,將單一 serif 改為完整 system font stack。
成功狀態
runtime 的 computed font-family 由 serif 改為包含 system-ui 與 sans-serif 的有序清單。
證據位置
runtime before=serif;after 字串包含 system-ui 與 sans-serif;property=font-family

能分辨 rem 依根元素、em 依目前字體尺度、ch 約依字形 0 寬度,並用 ch 限制段落行長。

為什麼重要
相對單位能跟隨使用者字級與元件尺度;適當行長可減少閱讀時跨行尋找下一行的負擔。
完成條件
根字級固定為 16px,將段落由 1em 切為 1.25rem 並同時加上 60ch 行長上限。
成功狀態
runtime 的 font-size 由 16px 變 20px;Styles 顯示 max-width:60ch,文字仍能在窄版縮排。
證據位置
runtime before=16px、after=20px、property=font-size;Styles 另可指出 padding:1em 與 max-width:60ch

能以 --surface、--text、--brand 等用途名稱取代元件內散落的固定色碼。

為什麼重要
用途型 token 把『這個色彩負責什麼』與實際色值分離,換品牌或主題時不必逐元件搜尋色碼。
完成條件
卡片元件只引用 var(),執行後在自身 scope 覆寫 --surface,觀察背景同步更新。
成功狀態
runtime 的 background-color 由 rgb(255, 255, 255) 變 rgb(217, 238, 243),元件宣告仍為 background:var(--surface)。
證據位置
runtime before=rgb(255, 255, 255)、after=rgb(217, 238, 243)、property=background-color

能以 data-theme 同步切換頁面、卡片、文字、邊框、按鈕與 focus token,且 localStorage 不可用時仍回退到 light。

為什麼重要
只改背景會留下低對比文字與看不見的 focus;若儲存例外中斷初始化,整個主題控制也可能失效。
完成條件
先以 light 開啟本機 fixture,再執行受控 runtime 把預覽根節點 data-theme 改為 dark。
成功狀態
runtime attribute 由 light 變 dark,完整預覽同步變色;儲存讀寫皆由 try/catch 包住,失敗時仍套用 light 並可手動切換。
證據位置
runtime before=light、after=dark、attribute=data-theme;視覺上 surface/text/border/action/focus tokens 同步改變,storage catch 不產生未處理錯誤

4. 4 個知識模組與 Micro Labs

依序展開;每次只改一項,操作後確認該模組自己的 DOM、progress 與 evidence。

1系統字型 fallback

完整解釋

白話:瀏覽器會從字型清單左邊開始找,找不到就往右用下一個,最後用通用字族保底。

正式說法:font-family 接受 prioritized family-name/generic-family list;user agent 使用第一個可提供所需 glyph 的可用字型。

何時使用:網站不嵌入 Web Font,或需要在不同作業系統維持穩定介面字型時。

修改前後:Before:只寫一個可能不存在的字型。After:清單包含系統 UI、常見中文字型與 generic sans-serif。

常見混淆:fallback stack 不保證整段文字只用一個實體字型;缺少某個 glyph 時仍可能逐字 fallback。

名詞與最小範例

系統字型 fallback System font fallback stack

白話
瀏覽器會從字型清單左邊開始找,找不到就往右用下一個,最後用通用字族保底。
正式定義
font-family 接受 prioritized family-name/generic-family list;user agent 使用第一個可提供所需 glyph 的可用字型。
何時使用
網站不嵌入 Web Font,或需要在不同作業系統維持穩定介面字型時。
最小範例
font-family:system-ui,-apple-system,"Segoe UI","Noto Sans TC",sans-serif;
驗收證據
runtime before=serif;after 字串包含 system-ui 與 sans-serif;property=font-family
常混淆
fallback stack 不保證整段文字只用一個實體字型;缺少某個 glyph 時仍可能逐字 fallback。

Micro Lab|系統字型 fallback

情境:服務頁在不同電腦顯示的中文字型差異很大,但專案不打算下載外部字型。

起始狀態:示範文字只使用 serif,未提供系統介面 fallback。

  1. 執行實驗替文字切換 system-stack class,讀取 font-family 前後值。
  2. 執行下方操作,觀察獨立的 DOM 與資料狀態。
  3. 依證據欄位重新驗收。

目前主題:系統字型 fallback

安心到府服務

尚未執行;DOM data-state=starter。

預期:runtime 的 computed font-family 由 serif 改為包含 system-ui 與 sans-serif 的有序清單。

證據:runtime before=serif;after 字串包含 system-ui 與 sans-serif;property=font-family

第一個檢查:在 Computed 搜尋 font-family,記錄完整清單而不是只看畫面像不像。

重設:重新載入 module,移除 system-stack class。

卡住時

  • 症狀:切換後 computed font-family 仍只有 serif。
    可能原因:system-stack class 未命中目標,或 font-family 被更高權重規則覆蓋。
    先檢查:查看目標 class list 與 Styles 中最後生效的 font-family。
    修正:讓 .system-stack 直接命中被測元素並移除衝突宣告。

理解檢查

fallback stack 最後為什麼要保留 generic family?

尚未作答。

查看解釋

generic family 讓 user agent 選擇該分類的可用字型。

本模組官方來源:MDN|font-family

2rem、em、ch 與可讀行長

完整解釋

白話:rem 看整頁基準,em 看目前文字大小,ch 適合描述一行大約能放多少字元寬度。

正式說法:rem 相對 root element font-size;em 在 font-size 上相對父層 computed font-size,在其他 property 上相對元素自身 font-size;ch 代表字型中 0 glyph 的 advance measure。

何時使用:建立可縮放字級、元件內相對間距與限制長篇段落閱讀寬度時。

修改前後:Before:所有字級與行長都寫固定 px。After:全域尺度用 rem、元件比例用 em、行長上限用 ch。

常見混淆:ch 不是精確的中文字數;不同字型與 glyph 寬度會影響實際每行內容。

名詞與最小範例

rem、em、ch 與可讀行長 Relative length units and readable line length

白話
rem 看整頁基準,em 看目前文字大小,ch 適合描述一行大約能放多少字元寬度。
正式定義
rem 相對 root element font-size;em 在 font-size 上相對父層 computed font-size,在其他 property 上相對元素自身 font-size;ch 代表字型中 0 glyph 的 advance measure。
何時使用
建立可縮放字級、元件內相對間距與限制長篇段落閱讀寬度時。
最小範例
.article-copy { font-size:1.25rem; padding:1em; max-width:60ch; }
驗收證據
runtime before=16px、after=20px、property=font-size;Styles 另可指出 padding:1em 與 max-width:60ch
常混淆
ch 不是精確的中文字數;不同字型與 glyph 寬度會影響實際每行內容。

Micro Lab|rem、em、ch 與可讀行長

情境:文章段落在大螢幕拉得太長,且元件 padding 不會跟著字級成比例調整。

起始狀態:父層字級 16px;段落 font-size:1em,沒有行長上限。

  1. 執行實驗切換 readable-scale class,讀取 font-size 由 1em 基準轉為 1.25rem 的 computed value。
  2. 執行下方操作,觀察獨立的 DOM 與資料狀態。
  3. 依證據欄位重新驗收。

目前主題:rem、em、ch 與可讀行長

這段內容用來比較 rem、em 與 ch。

尚未執行;DOM data-state=starter。

預期:runtime 的 font-size 由 16px 變 20px;Styles 顯示 max-width:60ch,文字仍能在窄版縮排。

證據:runtime before=16px、after=20px、property=font-size;Styles 另可指出 padding:1em 與 max-width:60ch

第一個檢查:先確認根元素與父層 computed font-size 都是 16px。

重設:重新載入 module,移除 readable-scale class。

卡住時

  • 症狀:1.25rem 的 computed font-size 不是 20px。
    可能原因:根元素 font-size 不是 16px,或更高權重規則覆蓋目標。
    先檢查:依序讀 documentElement 與目標的 computed font-size。
    修正:恢復 fixture 的 16px root 基準並讓 readable-scale 規則命中。

理解檢查

在 font-size 上,1rem 主要相對哪個值?

尚未作答。

查看解釋

rem 是 root em;font-size 上的 em 則依父層 computed font-size。

本模組官方來源:MDN|CSS values and units

3用途型 CSS token

完整解釋

白話:token 是有用途名稱的色彩插座;元件插 --surface,不必知道現在是白色還是深藍。

正式說法:CSS custom property 參與 cascade 並以 var() 取值;semantic token name 描述 UI role,而非綁定特定色相或明暗值。

何時使用:多個元件共用背景、文字、邊框、品牌或 focus 色,並可能換主題時。

修改前後:Before:每張卡各寫 #fff、#17324d。After:元件只引用 semantic tokens,scope 負責提供值。

常見混淆:--blue-500 是 palette token;--brand-action 才描述用途。兩者可分層,但不應把色名冒充用途。

名詞與最小範例

用途型 CSS token Semantic CSS custom property tokens

白話
token 是有用途名稱的色彩插座;元件插 --surface,不必知道現在是白色還是深藍。
正式定義
CSS custom property 參與 cascade 並以 var() 取值;semantic token name 描述 UI role,而非綁定特定色相或明暗值。
何時使用
多個元件共用背景、文字、邊框、品牌或 focus 色,並可能換主題時。
最小範例
:root{--surface:#fff;--text:#17324d}.card{background:var(--surface);color:var(--text)}
驗收證據
runtime before=rgb(255, 255, 255)、after=rgb(217, 238, 243)、property=background-color
常混淆
--blue-500 是 palette token;--brand-action 才描述用途。兩者可分層,但不應把色名冒充用途。

Micro Lab|用途型 CSS token

情境:首頁十張卡片都直接寫白底,現在品牌希望同時換成淡藍色表面。

起始狀態:卡片使用 var(--surface),根 scope 提供 #fff;未套 alternate-surface。

  1. 執行實驗切換 alternate-surface class,只覆寫 --surface,讀取同一元件的 computed background-color。
  2. 執行下方操作,觀察獨立的 DOM 與資料狀態。
  3. 依證據欄位重新驗收。

目前主題:用途型 CSS token

用途型 token 卡片
尚未執行;DOM data-state=starter。

預期:runtime 的 background-color 由 rgb(255, 255, 255) 變 rgb(217, 238, 243),元件宣告仍為 background:var(--surface)。

證據:runtime before=rgb(255, 255, 255)、after=rgb(217, 238, 243)、property=background-color

第一個檢查:在 Styles 確認 background 使用 var(--surface),不是另一個固定色碼。

重設:重新載入 module,移除 alternate-surface class。

卡住時

  • 症狀:覆寫 --surface 後卡片背景沒有變。
    可能原因:卡片仍使用固定 background-color,或 token 覆寫不在卡片可繼承的 scope。
    先檢查:查看卡片 Styles 的 background 來源與 --surface 定義位置。
    修正:改為 background:var(--surface),並在卡片自身或祖先 scope 提供 token。

理解檢查

哪一個名稱較符合用途型 token?

尚未作答。

查看解釋

surface 描述 UI 責任,實際值可隨品牌或主題改變。

本模組官方來源:MDN|Using CSS custom properties

4完整 Light/Dark 主題對照

完整解釋

白話:主題切換不是只關燈,而是一次換掉所有有責任的色彩;記憶功能壞掉時,頁面仍要能正常顯示與操作。

正式說法:Theme scope 以 data-theme 觸發 semantic custom properties cascade;持久化是可失敗的增強功能,核心 applyTheme 必須在 storage exception 下維持 deterministic fallback。

何時使用:網站提供手動明暗主題,或需要在系統偏好之外保存訪客選擇時。

修改前後:Before:按鈕只改 body background,文字與 focus 固定。After:六類 token 同步切換,aria-pressed 與狀態文字跟隨,儲存失敗不阻斷。

常見混淆:prefers-color-scheme 是系統偏好;手動 data-theme 是使用者明確選擇。若兩者並存,需先定義優先順序。

名詞與最小範例

完整 Light/Dark 主題對照 Complete light and dark theme toggle

白話
主題切換不是只關燈,而是一次換掉所有有責任的色彩;記憶功能壞掉時,頁面仍要能正常顯示與操作。
正式定義
Theme scope 以 data-theme 觸發 semantic custom properties cascade;持久化是可失敗的增強功能,核心 applyTheme 必須在 storage exception 下維持 deterministic fallback。
何時使用
網站提供手動明暗主題,或需要在系統偏好之外保存訪客選擇時。
最小範例
function safeRead(){try{const v=localStorage.getItem('theme');return v==='dark'?'dark':'light'}catch{return 'light'}} function safeWrite(v){try{localStorage.setItem('theme',v)}catch{}} function applyTheme(v){root.dataset.theme=v;toggle.setAttribute('aria-pressed',String(v==='dark'))}
驗收證據
runtime before=light、after=dark、attribute=data-theme;視覺上 surface/text/border/action/focus tokens 同步改變,storage catch 不產生未處理錯誤
常混淆
prefers-color-scheme 是系統偏好;手動 data-theme 是使用者明確選擇。若兩者並存,需先定義優先順序。

Micro Lab|完整 Light/Dark 主題對照

情境:深色模式只換了背景,卡片仍白、文字仍深藍、focus 幾乎看不見;隱私模式下 localStorage 又拋出例外。

起始狀態:預覽根節點 data-theme=light;完整 light tokens 生效,尚未執行 dark 切換。

  1. 執行實驗把主題預覽根節點的 data-theme 由 light 設為 dark;再以 safeRead/safeWrite 測試 storage 失敗仍回退 light。
  2. 執行下方操作,觀察獨立的 DOM 與資料狀態。
  3. 依證據欄位重新驗收。

目前主題:完整 Light/Dark 主題對照

主題預覽

背景、文字、邊框與操作都讀取 token。

尚未執行;DOM data-state=starter。

預期:runtime attribute 由 light 變 dark,完整預覽同步變色;儲存讀寫皆由 try/catch 包住,失敗時仍套用 light 並可手動切換。

證據:runtime before=light、after=dark、attribute=data-theme;視覺上 surface/text/border/action/focus tokens 同步改變,storage catch 不產生未處理錯誤

第一個檢查:先在 Elements 確認 data-theme=light,再逐項記錄 surface、text、border、action、focus 的 Computed 顏色。

重設:重新載入 module 回到 light;若 localStorage 無法使用,直接採 light,不阻止 runtime 操作。

卡住時

  • 症狀:localStorage 被阻擋後頁面初始化停止,主題按鈕也沒反應。
    可能原因:讀寫 storage 沒有 try/catch,或 applyTheme 被包在成功讀取之後才註冊。
    先檢查:在 DevTools 模擬 storage exception,查看 Console 第一個未處理錯誤。
    修正:讓 safeRead 失敗回傳 light、safeWrite 失敗靜默降級,並獨立執行 applyTheme 與事件綁定。

理解檢查

localStorage 讀取失敗時,核心主題功能應如何處理?

尚未作答。

查看解釋

持久化是增強;不應成為套用可讀主題與操作按鈕的必要條件。

本模組官方來源:MDN|Using CSS custom properties

5. 字型、尺度與主題 token|整合 Lab

情境:在同一份章節作品中整合 4 個 module,排除單項通過但組合失敗的問題。

交付:完成可切換且 Light/Dark 都具可讀性與鍵盤焦點的主題頁。

尚未執行整合步驟。
  1. 1觀察整合 Starter 並找出第一個未通過 module。

    檔案:files/starter/index.html 定位:#observe

    操作:逐項讀取 outcomes,執行一次現況操作。

    // OBSERVE:不修改程式,先記錄第一個失敗 moduleId。

    預期:能指出第一個未通過 module。

    證據:第一個 moduleId 與缺少的 evidence。

    原因:先觀察可避免未重現就直接改答案。

    卡住先查:確認開啟的是 files/starter/index.html。

  2. 2依 module 順序修改,一次只加入一個責任。

    檔案:files/starter/index.html 定位:#modify

    操作:執行實驗替文字切換 system-stack class,讀取 font-family 前後值。 → 執行實驗切換 readable-scale class,讀取 font-size 由 1em 基準轉為 1.25rem 的 computed value。 → 執行實驗切換 alternate-surface class,只覆寫 --surface,讀取同一元件的 computed background-color。 → 執行實驗把主題預覽根節點的 data-theme 由 light 設為 dark;再以 safeRead/safeWrite 測試 storage 失敗仍回退 light。

    // 06-01-fallback: 執行實驗替文字切換 system-stack class,讀取 font-family 前後值。
    // 06-02-rem-em-ch: 執行實驗切換 readable-scale class,讀取 font-size 由 1em 基準轉為 1.25rem 的 computed value。
    // 06-03-css-token: 執行實驗切換 alternate-surface class,只覆寫 --surface,讀取同一元件的 computed background-color。
    // 06-04-light-dark: 執行實驗把主題預覽根節點的 data-theme 由 light 設為 dark;再以 safeRead/safeWrite 測試 storage 失敗仍回退 light。

    預期:完成可切換且 Light/Dark 都具可讀性與鍵盤焦點的主題頁。

    證據:runtime before=serif;after 字串包含 system-ui 與 sans-serif;property=font-family;runtime before=16px、after=20px、property=font-size;Styles 另可指出 padding:1em 與 max-width:60ch;runtime before=rgb(255, 255, 255)、after=rgb(217, 238, 243)、property=background-color;runtime before=light、after=dark、attribute=data-theme;視覺上 surface/text/border/action/focus tokens 同步改變,storage catch 不產生未處理錯誤

    原因:逐項整合能把回歸定位到明確 module。

    卡住先查:回到第一個未通過的 module,只修該處。

  3. 3驗證所有 module 在整合後仍成立。

    檔案:files/solution/index.html 定位:#verify

    操作:重新整理並依 outcomes 順序重跑所有證據。

    // VERIFY:逐項記錄 pass/fail,不以看到畫面代替 evidence。

    預期:4/4 module 通過。

    證據:每個 moduleId 都有實際結果與重設後重現紀錄。

    原因:單項通過不代表整合後沒有 selector、id 或狀態衝突。

    卡住先查:若失敗,回到第一個失敗 module,不同時修多項。

  4. 4把本章責任轉用到不同內容情境。

    檔案:files/starter/index.html 定位:#challenge

    操作:替另一個商家情境重做,仍達成:完成可切換且 Light/Dark 都具可讀性與鍵盤焦點的主題頁。

    // CHALLENGE:只替換內容與資料,不新增框架或跳過 self-check。

    預期:完成可切換且 Light/Dark 都具可讀性與鍵盤焦點的主題頁。

    證據:轉用後的作品與全數 module self-check。

    原因:能轉用才代表理解責任,而非只記住原範例。

    卡住先查:先確認共同基底與 module id 未被改壞。

重設方法:重新載入 files/starter/index.html;若 localStorage 不可用,使用頁面內記憶狀態並提供重設按鈕。

6. 自學挑戰與三層提示

挑戰:將同一組責任轉用到另一個商家情境,仍達成:完成可切換且 Light/Dark 都具可讀性與鍵盤焦點的主題頁。

限制:不新增框架。;保留 module id 與驗收證據。

提交證據:提交可直接開啟的作品與逐項 self-check。

提示 1|方向提示

拆小:在「字型、尺度與主題 token」先完成第一個尚未通過的 module:系統字型 fallback,不同時修改其他責任。

提示 2|關鍵片段

串接:依 06-01-fallback → 06-02-rem-em-ch → 06-03-css-token → 06-04-light-dark 核對各自輸入與證據。

提示 3|完整解答與原因

完整解答與原因:依序執行 執行實驗替文字切換 system-stack class,讀取 font-family 前後值。 → 執行實驗切換 readable-scale class,讀取 font-size 由 1em 基準轉為 1.25rem 的 computed value。 → 執行實驗切換 alternate-surface class,只覆寫 --surface,讀取同一元件的 computed background-color。 → 執行實驗把主題預覽根節點的 data-theme 由 light 設為 dark;再以 safeRead/safeWrite 測試 storage 失敗仍回退 light。;這個順序能把每個結果對回單一 module,避免整合後無法定位。

完整解答預設收合;先留下自己的嘗試,再開第三層。

7. 症狀式除錯指南

8. 理解測驗、驗收證據與下一步

哪個狀態代表 06-01-fallback 完成?

尚未作答。

查看解釋

訪客不一定安裝相同字型;沒有 fallback 時,文字可能落到不合適的預設字型,影響可讀性與版面寬度。

哪個狀態代表 06-02-rem-em-ch 完成?

尚未作答。

查看解釋

相對單位能跟隨使用者字級與元件尺度;適當行長可減少閱讀時跨行尋找下一行的負擔。

哪個狀態代表 06-03-css-token 完成?

尚未作答。

查看解釋

用途型 token 把『這個色彩負責什麼』與實際色值分離,換品牌或主題時不必逐元件搜尋色碼。

哪個狀態代表 06-04-light-dark 完成?

尚未作答。

查看解釋

只改背景會留下低對比文字與看不見的 focus;若儲存例外中斷初始化,整個主題控制也可能失效。

驗收清單

官方來源

回到課程地圖選擇下一章