單元 2:CSS——把美感變成可維護的視覺系統

單元 2:CSS——把美感變成可維護的視覺系統

理解 cascade、盒模型與版面配置,將直覺上的好看轉成可重用、可響應、可驗收的視覺規則。

單元 2:CSS——把美感變成可維護的視覺系統

課程首頁:Learning/ai first frontend foundations

你可能很快就能判斷一個畫面「有點擠」、「層級不夠」、「按鈕太厚重」或「手機版節奏斷掉」。這種美感判斷非常重要,但 Agent 不能只靠感受穩定重現它。CSS 基本功的目的,是把你的視覺直覺翻譯成一組可以套用、比較與驗收的規則。

CSS 不是把每個元素調到好看就結束。真正可維護的 CSS 要回答:這個值為什麼存在?哪些地方應共用?內容變長、視窗變窄、字體載入失敗或狀態改變時,版面會如何反應?

本單元完成條件

完成後,你應該能夠:

  1. 用 cascade、specificity 與 inheritance 解釋「為什麼這條樣式沒有生效」。
  2. 用盒模型解釋一個元素的實際尺寸與留白來源。
  3. 判斷 Flexbox、Grid 與 position 適合處理哪一種關係。
  4. 把色彩、字體、間距、圓角與陰影整理成最小 design tokens。
  5. 用內容與版面行為描述 responsive 規則,而不只報裝置名稱。
  6. 驗收 hover、focus、disabled、loading、empty 與 error 等狀態。

CSS 的核心不是「屬性很多」,而是規則如何競爭

瀏覽器可能同時從多個地方取得同一元素的樣式:瀏覽器預設、全站樣式、component 樣式、狀態 class 或行內樣式。Cascade 負責決定最後採用哪一條。

當你看到「明明寫了顏色卻沒變」,先不要叫 Agent 再加一個更強的 selector 或 !important。依序檢查:

  1. selector 是否真的選到元素。
  2. 宣告是否被瀏覽器接受,有沒有拼字或值錯誤。
  3. 是否有其他規則在相同條件下勝出。
  4. 該屬性是否透過 inheritance 從父層而來。
  5. 當下是否正處於 media query、hover、disabled 等不同條件。

DevTools 的 Styles 面板會直接顯示套用與被劃掉的宣告,Computed 則顯示最後計算結果。這兩個證據通常比盯著 CSS 檔案猜測更可靠。

Specificity:不是分數遊戲,而是維護成本

Specificity 可以理解為 selector 的「優先權重量」。當規則越寫越具體,後續要覆寫它就必須使用更重的 selector,最後形成一路加碼的競賽。

健康的樣式系統通常偏好簡單、可預測的 component class 與狀態 class。若 Agent 為了修一顆按鈕,新增五層巢狀 selector 或多個 !important,你要問的是:是否有更上游的規則或 variant 應該被調整?這不只是程式潔癖,而是避免下一次小改動影響難以預測。

Inheritance:讓共同特徵自然向下傳遞

文字顏色、字體等部分屬性常會從父元素傳給子元素,尺寸與邊框等則通常不會。Inheritance 能讓整個區塊共享文字風格,但也可能造成「這裡怎麼拿到奇怪顏色」的困惑。

判斷時先在 Computed 面板追溯來源,不要看到結果就直接在子元素重設。過度重設會讓全站主題、深色模式與 component variant 更難維護。

盒模型:每個元素都是一個佔空間的盒子

一個盒子由四層組成:content、padding、border、margin。

  • Content 是文字、圖片或子元素實際放置的區域。
  • Padding 是內容與邊框之間的內部空間,也會影響背景與可點擊範圍。
  • Border 是盒子的邊界。
  • Margin 是盒子與外部其他盒子的距離。

box-sizing: border-box 會讓宣告的 width/height 包含 padding 與 border,通常更容易預測。若一個寬度 100% 的元件又因 padding 超出容器,不要只修成 98%;先確認盒模型與父容器限制。

留白要表達關係

Margin 與 padding 不能只靠「看起來差不多」亂填。內部項目彼此緊密、不同群組之間較疏,能讓使用者不看邊框也理解分組。好的 spacing system 通常使用有限的級距,例如小、中、大與段落級距,而不是每個區塊各有一個無法解釋的數字。

Flexbox、Grid 與 position:先判斷關係,再選工具

Flexbox 處理一個主要方向

當內容主要沿著橫向或直向排列,並需要對齊、分配剩餘空間或換行時,Flexbox 很合適。例如導覽列、按鈕群、頭像加文字、直向表單。

Grid 處理列與欄的共同關係

當你需要同時控制橫列與直欄,例如卡片網格、資料面板或明確版面區域,Grid 更自然。Grid 能描述「欄位如何分配」,不需要用固定寬度假裝成響應式。

Position 處理疊放與特定參考位置

Absolute、fixed 與 sticky 適合徽章、覆蓋控制、固定工具列或黏附標題等情境。它們會改變正常排版關係,不應用來完成所有對齊。大量絕對定位常表示版面只在一個固定尺寸看起來正確,內容一變就互相覆蓋。

選擇前問:元素應該跟著內容流動嗎?只需要單方向排列,還是需要列欄共同對齊?是否真的要脫離一般排版?

從美感直覺建立 Design tokens

Design tokens 是有名稱的設計決策。它把「這裡大概 18px」改成「這是內文行高」或「這是區塊間距」。名稱讓人與 Agent 都能理解值的角色。

最小系統可以先包含:

  • 色彩:背景、表面、主要文字、次要文字、邊界、強調、成功、警告、錯誤。
  • 字體:字型家族、內文、輔助文字、標題級距與行高。
  • 間距:控制元件內距、同組項目距離、不同段落距離與大區塊距離。
  • 形狀:小/中/大圓角與完整膠囊形。
  • 深度:少量可說明用途的 shadow,而不是每個卡片一種陰影。
  • 動態:常用 duration、easing 與 reduced motion 行為。

Token 不是為了把所有數字變成變數。只有會重複、承載設計意義或需要全域協調的值才值得命名。過度 token 化會讓簡單修改必須穿越很多抽象層。

Responsive 不是做三張縮小版截圖

Responsive design 的重點,是內容與操作在不同可用空間下如何重排。裝置名稱只是測試方便,真正的 breakpoint 應在版面開始無法維持時出現。

以三欄卡片為例,可以描述成:

  • 容器足夠時三欄,卡片維持最低可讀寬度。
  • 空間不足時改兩欄,而不是把文字擠成狹長直條。
  • 更窄時單欄,圖片比例與按鈕觸控範圍仍合理。
  • 標題允許換行,卡片高度不靠固定文字行數維持假整齊。

這種描述比「桌機 1440、平板 768、手機 375」更有用,因為它說明了為什麼要改版面。

狀態是設計的一部分

一個按鈕不只有預設樣式。完整元件至少要考慮:

  • Hover:滑鼠目前指向,但尚未操作。
  • Focus:鍵盤或其他輸入方式目前定位在此。
  • Active:正在按下或觸發的瞬間。
  • Disabled:目前不能操作,並需要有原因或前置條件。
  • Loading:工作已開始但尚未完成。
  • Error:工作失敗,需要說明與恢復方式。

頁面也有 initial、empty、partial、success 等狀態。只設計資料完整的理想畫面,Agent 就會自行猜測其他狀態,結果往往在真實使用時破壞版面。

與 Agent 協作:把「做得高級」拆成規格

與其說「整體更有質感」,可以說:

保留現有資訊架構與內容,不新增宣傳文案。先盤點目前色彩、字體、間距、圓角與陰影,整理重複值與例外值。

請提出最小 token 集合,並說明每個 token 對應的視覺角色。版面需在內容變長、320px 窄畫面、平板與桌面時保持可讀;breakpoint 依版面失效位置決定。

實作範圍只限指定頁面與必要共用 token。完成後提供:
1. token 前後差異;
2. default/hover/focus/disabled/loading/error 狀態;
3. 指定寬度的實際畫面證據;
4. Elements/Styles 中關鍵規則;
5. 沒有處理的例外與原因。

操作練習:把一頁整理成最小視覺系統

  1. 選一頁你認為美感基本正確,但規則可能零散的介面。
  2. 記錄所有文字大小、主要顏色、間距、圓角與陰影。
  3. 將它們按用途分組,不急著合併相近數字。
  4. 找出真正重複且角色相同的值,形成最小 tokens。
  5. 寫出每種 component 的狀態矩陣。
  6. 在窄手機、寬手機/平板、桌面寬度放入較長真實文字。
  7. 讓 Agent 進行一個小範圍整理,然後比較 diff 與畫面。
  8. 用 DevTools 確認不是靠 !important、固定高度或 overflow 隱藏問題。

常見誤判

  • 「像素完全相同才是設計還原」:內容與字型變動下,系統應維持關係,不是只適合一張截圖。
  • 「所有距離都用同一個數字最一致」:不同層級需要不同節奏,一致是可解釋的尺度,不是單一值。
  • 「media query 越多越完整」:規則太碎可能代表 component 缺乏自然伸縮能力。
  • 「加 !important 就修好了」:它常把來源問題推遲到下一次修改。
  • 「disabled 降低透明度即可」:使用者仍需要知道不能操作的原因,文字對比也可能因此不足。
  • 「有手機截圖就是 responsive」:長文字、鍵盤、橫向、放大與中間寬度仍可能失敗。

Teach-back 題目

  1. Cascade、specificity 與 inheritance 分別如何影響最後樣式?
  2. 為什麼一個宣告 width: 100% 的元素仍可能超出容器?
  3. 你會如何判斷一個卡片區要用 Flexbox 還是 Grid?
  4. Design token 和把所有數字改成變數有什麼不同?
  5. Responsive 規格為什麼應描述版面行為,而不只列裝置寬度?

上一課:Learning/ai first frontend foundations/01 html dom semantic structure

下一課:Learning/ai first frontend foundations/03 javascript data and async

相關筆記