你的 AI Agent 正在製造 CSS 垃圾山:Syntax.fm 兩位老司機的維護性 CSS 心法

你的 AI Agent 正在製造 CSS 垃圾山:Syntax.fm 兩位老司機的維護性 CSS 心法

發布於
·18 分鐘閱讀
AIAI Agent前端開發CSSPodcast產業觀察工具開源創業商業

TL;DR

  • 網站「腐爛」最明顯的徵兆是某個地方突然冒出 Times New Roman,這通常代表底下的 CSS 已經是一團亂麻
  • AI coding agent 是新一代的 CSS slop 製造機:到處硬寫顏色值、為每個 breakpoint 重寫一遍 media query、隨手生出四十種 box shadow
  • Tailwind、Atomic CSS、StyleX、CSS Modules、Component-scoped CSS 各有各的擁護者,重點不是選哪個,而是選了就要從一而終
  • 變數(custom property)是現代 CSS 的核心工具,搭配 @property@layer@scope 可以解決過去十年讓人抓狂的問題
  • 對付 AI 寫 CSS 最有效的方式是 deterministic guardrails:用 StyleLint 規則直接 block 掉「非變數的顏色值」,比寫一萬字的 prompt 都有用

關於這集 Podcast

這集在 2026 年 4 月 27 日上線的是 Syntax.fm 的第 999 集,Wes Bos 和 Scott Tolinski 兩位主持人聊「怎麼寫出可維護的 CSS」。Syntax.fm 是英語圈前端開發者最常聽的 podcast 之一,每週兩集,主題從 JavaScript、CSS、TypeScript 到各種框架都有,調性走「有點碎嘴但很有料」的路線。

Wes Bos 是加拿大的全端開發者和教育者,自己賣過一堆熱門課程(JavaScript 30、Beginner JavaScript、Advanced React 等等),個人品牌做得非常大,是英語圈很少數靠「教 web 開發」就能養活整個事業的人。Scott Tolinski 則是 Level Up Tutorials 的創辦人,這個教學平台後來被 Sentry 收購了,他自己是底特律出身、跳街舞跳了三十幾年的開發者,podcast 開頭還在抱怨他四十歲後第一次回去練舞「身體完全不一樣了」。

兩個人的搭配很經典:Wes 偏向系統化、什麼都先用變數定義好;Scott 自稱「slop master」,對 CSS 的態度比較自由,但兩人在「怎麼把 AI 的 CSS 爛習慣壓下來」這件事上意外地有共識。

這集對我來說有意思的點不只是 CSS 技術本身,而是它意外地變成一集「怎麼用 deterministic 工具管教你的 AI agent」的實戰課。如果你最近在用 Cursor、Claude Code 或 Codex 寫前端,這集講到的東西基本上就是你會踩到的所有坑。

CSS 「腐爛」的徵兆,從 Times New Roman 說起

Wes 開場講了一個我覺得超精準的觀察:你怎麼判斷一個網站的 CSS 已經爛掉?看到某個地方突然冒出 Times New Roman、或是某個莫名其妙的字型混進來,那就是訊號。底層的 CSS 一定亂得可以。

腐爛的核心原因其實就兩件事:

第一,樣式漏到不該漏的地方。 一個 component 應該要能單獨拿出來放到一張白紙上,看起來還是長得差不多。如果你把它從某個 layout 裡面抽出來就壞掉,那代表 CSS 的耦合已經失控了。

第二,每個 component 都被綁得太死,反而失去了 cascade 的好處。 Wes 講的這點很到位:「現在大家做的剛好相反,每個 component 都是 tight unit,所以你想做一個全站性的調整,這些 tight unit 全部還停留在舊樣式。」

這個矛盾其實就是現代 CSS 的兩難。你既要 component 隔離(不要漏出去),又要保留 cascade(不要每個都得個別改),中間的拿捏就是維護性 CSS 的全部功課。

AI 是新一代的 CSS slop 製造機

兩個主持人講到 AI 寫 CSS 的時候,我整集聽下來都在點頭。AI coding agent 有幾個經典的爛習慣:

爛習慣一:到處硬寫顏色值。 你跟它說「用變數」,它就把 var(--blue) 拿出來,但下一秒它又用 oklch(from var(--blue) calc(l * 0.9) c h) 在現場算一個新顏色。技術上沒違反規則,但你的顏色系統就是會慢慢長出一堆衍生變體,最後想做 dark mode 或主題切換的時候你就哭了。

爛習慣二:為每個 breakpoint 重寫一遍 selector。 AI 特別愛把 media query 寫在最外層,然後在每個 breakpoint 裡把所有 selector 全部重寫一次。Wes 說他寧可把 media query nest 在 selector 裡面,因為要 debug 的時候不用在 11 個 media query 裡跳來跳去。

爛習慣三:隨手生出四十種 box shadow。 沒有變數約束的情況下,AI 每次需要陰影就生一個新的 shadow 值。一個禮拜後你的網站就有四十種陰影,沒有任何兩個是一樣的。

這些不是 AI 故意搞你,而是它的訓練資料裡本來就充滿這種寫法。如果你不主動加護欄,它就是會這樣寫。

這個現象其實跟我之前寫過的 AI Coding 時代:軟體產品開發變成咒術迴戰 是同一個邏輯:AI 給了你領域展開的能力,但如果你沒有自己的術式約束,最後展開出來的就是亂七八糟的東西。

五大方法論的取捨:選哪個其實沒那麼重要

整集講到的方法論可以整理成這張表:

方法代表工具優點痛點
Utility CSSTailwind系統內建、scoping 自動解決、上手快一個 element 上一堆 class、想客製化要繞路
Atomic CSSUnoCSS可以自己組一個 Tailwind、引擎更彈性維護成本高、社群比 Tailwind 小
StyleXFacebook 開源同樣的 CSS 不會 ship 兩次、bundle 超精簡media query 語法反人類、要寫 JS object
CSS ModulesVite/Next 內建scoping 清楚、不依賴框架CSS 跟 component 分檔、nest 子元素麻煩
Component-scoped CSSSvelte/Vue SFC可以寫 divstrong 不用想 class 名、寫起來最爽跨 component 共用樣式有點卡

Wes 個人最喜歡 component-scoped CSS(特別是 Svelte 的單檔元件),Scott 則是重度 global CSS 使用者,自己甚至寫了一個叫 graffiti 的 UI library。

整集講下來的結論其實就一句話:選哪個系統真的不重要,重要的是你選了就要從一而終。 最會出事的就是「這個 component 用 Tailwind、那個用 CSS Modules、再來一個是 styled-components」這種混搭,幾個月後你會看不出整個專案的設計邏輯在哪裡。

我自己的觀察是:solo 開發或小型團隊用 Tailwind 最不用想,因為它已經幫你把系統搭好了。但如果是中大型團隊、或是要做 design system 給多個產品共用,你會想要更接近原生 CSS 的方案,這樣才能享受到 @scope@layer 這些新東西。

變數、@property、Layer、Scope:現代 CSS 的四大核心

Scott 在這集講變數的時候有一句話我很贊同:「我把所有東西都丟進變數,不管是什麼類型的東西。」

現代 CSS 的變數跟以前 SASS 的變數完全不是同個東西。SASS 變數是 build time 替換,CSS custom property 是 runtime 動態值,可以 cascade、可以被 JavaScript 改、可以做動畫、可以拿去算 calc。差距是十倍以上。

這集講到幾個變數的進階用法值得記下來:

@property 給變數加上型別。 這個東西很多人沒在用,但它可以讓你的 CSS 變數有型別檢查,還能讓 <number><color> 類型的變數變成可動畫的。Scott 還特別提到 inherits: false 這個設定,它能解決一個很煩的問題:如果你在父層 layout 設了一個 --gap,預設這個變數會 cascade 到所有子層,包含子層的 layout,整個畫面就會被你不小心改大。把 inherits 設為 false,變數就只作用在當層,不會繼承下去。這個 trick 對於做 component variant 超好用。

@layer 解決 cascade 的優先權地獄。 你應該有遇過那種「明明 .heading 沒效,要寫成 h2.heading 才生效」的情況。CSS layers 讓你直接告訴瀏覽器:這幾個層的樣式是按這個順序套用的,不要管 specificity。Tailwind 4 整個就是建在這個概念上。Wes 也吐槽說:「!important 還是會打贏 layer,這個東西就是 final boss。」

@scope 終於能在原生 CSS 做 scoping。 這是 2025 年 12 月後 Firefox 也支援的新功能,你可以直接在某個元素裡塞 <style> 標籤、寫 @scope { ... },CSS 就會自動 scope 到那個父元素。更狂的是它還有「donut scope」,可以指定 scope 的開頭跟結束,例如「從 .card 開始,但遇到下一個 .card 就停」。這對於 nested card 這種場景簡直是救命。

這些東西過去都是各個框架自己做的,現在原生 CSS 全部支援了。Wes 說:「以後 Vue、Svelte 應該都會慢慢遷移到原生 scoping,少一個東西要自己維護。」

StyleLint 是你 AI agent 的剎車

整集最關鍵的一段是 Scott 講他怎麼擋住 AI 的爛習慣:

我設了一個 StyleLint 規則:如果一個顏色值不是 custom property,就 error。這樣 AI 就不能在原地硬算顏色,它必須去用變數。

這就是我一直在講的 deterministic tools 的價值。你跟 AI 講「不要硬寫顏色」一萬次,它還是會偶爾失手。但你在 lint 規則裡寫死「非變數顏色一律報錯」,它就連寫都寫不出來。

這集還提到 Biome 也內建了 CSS linter,ESLint 也有對應的 plugin,所以不管你用什麼 stack 都有工具可以接。重點是把這些護欄架起來,特別是團隊大、AI 協作多的時候。

這個邏輯跟我之前寫過的 前 OpenAI 工程師的 Coding Agent 使用心法 講的 context 管理是同一件事:你不是要 AI 變得更聰明,你是要把它能犯錯的空間縮小。

Fluid typography:一個 clamp 解決所有 RWD

Scott 介紹了他自己做的 fluid-type.tolin.ski,這個工具會幫你產出一個超大的 clamp() function,讓字級在不同螢幕尺寸下自動流動。

核心邏輯是:你定義一個基準字級(例如桌面 20px、手機 14px),加上一個比例因子,工具會幫你算出所有層級字級的 clamp 公式。從那之後你的字級就完全不需要寫 media query 了。Wes 說這個東西他不用,因為他常常就只想「給我一個 18px 字級不要算來算去」,但承認對於要建設計系統的人來說超好用。

這段還有一個 Wes 補充的概念:有人在 Twitter 上問「為什麼大家都用 px 或 rem 設字級,不用 line-height 為單位?」現在 CSS 有 lhchex 這些新單位,全站用 lh 做垂直節奏會超漂亮。這個梗早期 SASS 的 Compass 套件就玩過了(叫 vertical rhythm),現在原生 CSS 也能做。

Embrace the flex:少寫 media query 是門功夫

兩個人都很強調這件事:寫 media query 是新手才做的事。

進階寫法是用 min()max()clamp()auto-fitauto-fillgrid-template-columns: repeat(auto-fit, minmax(250px, 1fr)) 這些東西,讓 layout 自己根據容器大小流動。

如果真的要寫 media query,至少改用 container query@container)而不是 viewport-based 的 @media,因為現代 web 的 component 常常要在不同 layout 裡 reuse,container query 才是對的抽象層級。

而且如果你要寫斷點,不要在斷點裡重寫整套 selector,只要更新 CSS 變數就好。例如:

.layout {
  --columns: 4;
  --base-font: 16px;
  
  @media (max-width: 768px) {
    --columns: 2;
    --base-font: 14px;
  }
  
  grid-template-columns: repeat(var(--columns), 1fr);
  font-size: var(--base-font);
}

這樣不管 RWD 怎麼變,你的邏輯永遠只在一個地方。

老技術中的 BEM:致敬一下就好

聊到方法論的時候不能不提 BEM(Block Element Modifier)。Wes 自己說 AI 還是很愛叫他用 BEM,他每次都拒絕:「我不需要這個了。」

BEM 在沒有 component scoping 的時代是必要之惡,靠 naming convention 來避免 CSS 衝突。但現在 component-scoped CSS、CSS Modules、@scope 都解決這個問題了,BEM 那種 block__element--modifier 的命名法在 2026 年看起來就是繁瑣。

這也是為什麼我說 AI 寫 CSS 容易過時:它的訓練資料裡有大量 2018-2022 年的 best practice,那時候 BEM 還是主流。現在你必須主動用 system prompt 或 lint 規則告訴它「不要用 BEM」,否則它就是會用。

維護性 CSS 的核心原則整理

把整集的精華濃縮成幾條原則:

  1. 顏色、字級、陰影、間距全部用變數,禁止硬寫值。用 StyleLint 規則去 block AI 的爛習慣
  2. 變數可以 derive,不要重複定義。用 color-mix() 或相對顏色語法從 base color 算出 hover、border、shadow 的顏色,而不是定義五個獨立的顏色變數
  3. base CSS 要讓沒有 class 的 HTML 也長得體面。Scott 偏好這種、Wes 偏好 reset 到底再建設。兩種都行,重點是要有立場
  4. 善用 @layer 控制 cascade 順序,避免 specificity 戰爭
  5. 善用 @scope 取代繁瑣的 BEM 命名
  6. 少寫 media query,多用 clamp()auto-fit、container query
  7. 用 lint 工具當 AI 的剎車,比寫長 prompt 有效一百倍

寫在最後

這集表面上是在聊 CSS,實際上講的是「如何在 AI 寫程式的時代維持程式品質」。CSS 因為它的 declarative 特性,特別容易暴露 AI 的爛習慣,但同樣的邏輯可以套到任何語言、任何 framework 上。

我自己的體會是:AI 不會幫你建立品味,它只會放大你既有的習慣。如果你本來就會用變數、會分 layer、會設 lint 規則,AI 就會跟著你的習慣產出乾淨的 code。如果你本來就什麼都硬寫,AI 也會跟著你硬寫,而且寫得比你快十倍,垃圾也累積得比你快十倍。

deterministic guardrails 是這個時代的真正核心技能。哪些事情你能交給 AI、哪些事情你必須用工具強制約束,這個判斷力會直接決定你的程式碼能不能撐過半年。

如果這篇對你有幫助,wilsonhuang.xyz 上還有更多關於 AI coding 和 deterministic tooling 的觀察,歡迎訂閱。

Sources:

推薦閱讀

喜歡這篇文章嗎?

訂閱電子報,每週收到精選技術文章與產業洞察,直送你的信箱。

💌 隨時可以取消訂閱,不會收到垃圾郵件