單元 2:CSS——把美感變成可維護的視覺系統
理解 cascade、盒模型與版面配置,將直覺上的好看轉成可重用、可響應、可驗收的視覺規則。
單元 2:CSS——把美感變成可維護的視覺系統
你可能很快就能判斷一個畫面「有點擠」、「層級不夠」、「按鈕太厚重」或「手機版節奏斷掉」。這種美感判斷非常重要,但 Agent 不能只靠感受穩定重現它。CSS 基本功的目的,是把你的視覺直覺翻譯成一組可以套用、比較與驗收的規則。
CSS 不是把每個元素調到好看就結束。真正可維護的 CSS 要回答:這個值為什麼存在?哪些地方應共用?內容變長、視窗變窄、字體載入失敗或狀態改變時,版面會如何反應?
本單元完成條件
完成後,你應該能夠:
- 用 cascade、specificity 與 inheritance 解釋「為什麼這條樣式沒有生效」。
- 用盒模型解釋一個元素的實際尺寸與留白來源。
- 判斷 Flexbox、Grid 與 position 適合處理哪一種關係。
- 把色彩、字體、間距、圓角與陰影整理成最小 design tokens。
- 用內容與版面行為描述 responsive 規則,而不只報裝置名稱。
- 驗收 hover、focus、disabled、loading、empty 與 error 等狀態。
CSS 的核心不是「屬性很多」,而是規則如何競爭
瀏覽器可能同時從多個地方取得同一元素的樣式:瀏覽器預設、全站樣式、component 樣式、狀態 class 或行內樣式。Cascade 負責決定最後採用哪一條。
當你看到「明明寫了顏色卻沒變」,先不要叫 Agent 再加一個更強的 selector 或 !important。依序檢查:
- selector 是否真的選到元素。
- 宣告是否被瀏覽器接受,有沒有拼字或值錯誤。
- 是否有其他規則在相同條件下勝出。
- 該屬性是否透過 inheritance 從父層而來。
- 當下是否正處於 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. 沒有處理的例外與原因。
操作練習:把一頁整理成最小視覺系統
- 選一頁你認為美感基本正確,但規則可能零散的介面。
- 記錄所有文字大小、主要顏色、間距、圓角與陰影。
- 將它們按用途分組,不急著合併相近數字。
- 找出真正重複且角色相同的值,形成最小 tokens。
- 寫出每種 component 的狀態矩陣。
- 在窄手機、寬手機/平板、桌面寬度放入較長真實文字。
- 讓 Agent 進行一個小範圍整理,然後比較 diff 與畫面。
- 用 DevTools 確認不是靠
!important、固定高度或 overflow 隱藏問題。
常見誤判
- 「像素完全相同才是設計還原」:內容與字型變動下,系統應維持關係,不是只適合一張截圖。
- 「所有距離都用同一個數字最一致」:不同層級需要不同節奏,一致是可解釋的尺度,不是單一值。
- 「media query 越多越完整」:規則太碎可能代表 component 缺乏自然伸縮能力。
- 「加
!important就修好了」:它常把來源問題推遲到下一次修改。 - 「disabled 降低透明度即可」:使用者仍需要知道不能操作的原因,文字對比也可能因此不足。
- 「有手機截圖就是 responsive」:長文字、鍵盤、橫向、放大與中間寬度仍可能失敗。
Teach-back 題目
- Cascade、specificity 與 inheritance 分別如何影響最後樣式?
- 為什麼一個宣告
width: 100%的元素仍可能超出容器? - 你會如何判斷一個卡片區要用 Flexbox 還是 Grid?
- Design token 和把所有數字改成變數有什麼不同?
- Responsive 規格為什麼應描述版面行為,而不只列裝置寬度?
上一課:Learning/ai first frontend foundations/01 html dom semantic structure
下一課:Learning/ai first frontend foundations/03 javascript data and async