佈局
這個技能決定元素的位置、間距與尺寸。它會以相同的順序做出相同的決定,並根據實際渲染出的像素來檢查結果。
在任何決策之前,請套用 product-ui §1(品味日誌與優先順序)及 §5(哪些技能負責哪些部分)。
依此順序決定
- 外殼 (Shell)。 為畫面的各個區域命名:側邊欄、工具列、內容區、檢查器。每個區域最多只能有一個滾動擁有者或無。詳細內容與寬度:shell-and-responsive.md。原因:外殼決定了內部佈局必須容納的每一種寬度。
- 寬度與邊長 (Width and Measure)。 根據內容設定內容欄:表單 440px,閱讀區 720px 且內文以
type的邊長 (66ch) 為上限,資料區 1200px。欄位絕不因畫面寬而伸長。原因:超過 75 個字元會讓讀者迷失方向,而拉伸的表單會讓視線在畫面上遊移。 - 空間 (Space)。 只使用下方的比例尺,不使用其他值。先用空間分組;當空間無法承載時,才添加底色,然後才是分隔線。spacing-and-density.md。
- 對齊 (Align)。 選取共同的邊緣,並將所有元素放在那些邊緣上。1–4px 的偏差即為錯誤。在視覺上幾何形狀看起來不對的地方,進行光學校正。spacing-and-density.md。
- 尺寸 (Size)。 為該區域選擇密度模式,然後再設定點擊區域。spacing-and-density.md。
- 最差情況資料 (Worst-case Data)。 在宣告完成前,為最長、最短、缺失、翻譯後以及仍在載入中的內容調整盒模型大小。stability-and-text.md。
- 適配 (Adapt)。 讓結構對其容器做出響應,而不是對裝置做出響應。shell-and-responsive.md。
- 測量 (Measure)。 以 2 倍解析度渲染,測量間隙與邊緣,並修正像素所顯示的問題。measure.md。
比例尺
單一比例尺,4px 基底。絕不撰寫不在步驟中的間距值。原因:非標準值 (10px, 14px, 18px) 會讓韻律無法察覺也無法維持。
| Token | px | 典型用法 |
| --- | --- |
| --space-xs | 4 | 標籤與其說明之間;標籤與圖示之間 |
| --space-sm | 8 | 群組內項目之間;控制項 padding 區塊方向 |
| --space-md | 12 | 有邊框的控制項之間;控制項 padding 內聯方向 |
| --space-lg | 16 | 欄位之間;緊湊卡片 padding |
| --space-20 | 20 | 卡片內區塊之間 |
| --space-xl | 24 | 群組之間;舒適卡片 padding |
| --space-2xl | 32 | 區段之間 |
| --space-3xl | 48 | 頁面第一個標題上方 |
| --space-4xl | 64 | 頁面最後一個區塊下方 |
1–2px 的光學對齊微調是唯一不在比例尺上的值。為它們命名 (--play-nudge: 2px)。
無需參考文件的規則
- 用空間分組。 組與組之間的間隔至少是組內間距的 2 倍:組內 8px 表示組間 16px 或更多。各處間距相等會使分組變得模糊,而這種模糊是功能性錯誤。
- 先移除線,再添加線。 先使用空間,其次是底色,最後才是分隔線,且僅用於密集資料。原因:每一條線都需要注意力,而空間能免費提供相同的資訊。
- 兩者之間的元素,其上方的空間等於下方的空間。 原因:一個更靠近某個相鄰元素的事物,會被認為屬於該元素。
- 控制項必須看起來像控制項。 按鈕、連結或切換開關具有形狀、底線或工具列位置。靜態文字永遠不應使用這些樣式。原因:偽裝成文字的控制項會被隱藏,而偽裝成控制項的文字會收集無效點擊。
- 動作應位於其所操作的資料上方,絕不位於其下方。設定某個項目的控制項應放在其旁邊。
- 主要內容放在前方並處於起始側。 視覺順序等於 DOM 順序:永遠不要使用
order、row-reverse或網格放置來重新排列內容。原因:鍵盤與螢幕閱讀器的順序遵循 DOM。 - 對於需要鏡像翻轉的情況,使用邏輯屬性(
margin-inline-start、padding-block、inset-inline-end、text-align: start)。原因:如此一來,無需第二張樣式表即可支援 RTL。 - 切勿將關鍵控制項放在視窗或側邊欄的底部邊緣。 原因:使用者有時會將視窗部分移出螢幕。
- 滾動顯示了被隱藏的內容。 超出邊緣的內容應有可見的提示:16–32px 的預覽或漸層淡出效果。
- 沒有任何東西會偏移。 懸停、選取和聚焦永遠不應改變元素的大小。已載入的資料應佔用骨架所佔的盒模型。
常見問題對照表
| 錯誤寫法 | 正確寫法 | 原因 |
|---|---|---|
在表單中使用 gap: 12px 且在其 fieldset 之間使用 gap: 12px | 內部使用 gap: 8px,之間使用 gap: 24px | 群組之間需要至少 2 倍的間隔 |
padding: 10px 14px | padding: var(--space-sm) var(--space-md) | 只能使用比例尺步驟 |
| 標題 x=24,描述 x=26 | 兩者都在 x=24 | 1–4px 的偏差會被視為明顯的錯誤 |
16×16 的圖示按鈕,padding: 0 | 16px 的字形,::before { inset: -12px } 以獲得 40px 的目標 | 滑鼠目標應為 40px |
.row + .row { margin-top: 8px } 搭配行上的懸停與點擊 | .row { padding-block: 4px },無 margin | 行之間的間隙會變成死區並使懸停效果閃爍 |
.menu { width: 200px } | width: max-content; min-width: 160px; max-width: 320px | 選單應貼合其最長標籤的寬度 |
在卡片上使用 @media (max-width: 768px) | 在卡片的上層元素上使用 @container (width < 400px) | 位於窄側邊欄中的卡片應根據自身寬度響應,而非根據畫面寬度 |
img 無尺寸,隨後卡片跳動 | 設定 width、height 或 aspect-ratio | 在圖片位元組載入前,盒模型就已存在 |
| 固定頁首,定位點落在其下方 | 在滾動容器上設定 scroll-padding-block-start: 56px | 焦點與定位點永遠不會位於 chrome 之下 |
完成前
在 diff 中搜尋以下模式,然後進行驗證。
| 模式 | 修正 |
|---|---|
margin、padding 或 gap 的值不在比例尺內 | 取最接近的步驟值 |
在內部與群組之間使用了相同的單一 gap 值 | 內部值 ≤ 之間值的一半 |
純圖示按鈕小於 24px,或小於 40px 但無 ::before | 使用偽元素擴大 |
在可點擊的行或選單項目之間使用 gap 或 margin | 將 padding 加在項目內部 |
在選單、按鈕或文字框上使用固定 width | 使用 max-content、min-width、max-width;文字框使用 min-height |
在可重複使用的元件內部使用 @media (max-width... | 在其上層元素上使用 @container |
外殼使用 height: 100vh | 改用 100dvh |
包含長文字的 1fr 軌道或 flex 子項 | 使用 minmax(0, 1fr) 或 min-width: 0 |
在需鏡像的 UI 中使用 margin-left、padding-right、left: 0、text-align: left | 改用邏輯屬性形式 |
固定頁首或頁尾沒有 scroll-padding-block | 將其設為 chrome 的高度 |
| 骨架行的高度或欄寬度與已載入的內容不同 | 使它們匹配 |
img 沒有尺寸或 aspect-ratio | 添加一個 |
使用 measure.md 進行驗證:以 2 倍解析度在 390px 與外殼的最大寬度下渲染,執行指令碼,裁剪每個候選區塊並計算像素。然後在固定 chrome 存在的情況下,使用 Tab 鍵遍歷畫面,並載入最長的字串。將無法檢查的項目回報為「未驗證」。