本頁目錄
一般 CSS 與 CSS Modules 可以依專案需要,長期與 Master CSS 並存。將遷移視為整理工作,不必要求每一個宣告都變成 class。
將重複、純視覺且適合由標記負責的樣式移到 Master CSS class。全域重設、第三方樣式、複雜選擇器、生成的 CSS 與難以閱讀的一次性規則,則保留在一般 CSS。
#遷移策略
選擇一個能檢查原始 CSS 與渲染狀態的小元件。依共用遷移流程建立基準並檢查每批工作;本指南著重判斷哪些宣告適合放進標記,哪些局部 CSS 契約應該保留。
理想的第一批工作有穩定的視覺宣告,不涉及第三方選擇器。若模組的局部 class 名稱是 API、需要選取內部結構,或替換後會讓複雜選擇器散落各使用處,就保留模組。
改變撰寫方式,保留結果
比較宣告與計算結果。token 名稱相似還不夠,值、配色與互動狀態都要一致。
文字說明
- 先確認值. CSS 規則提供 1rem 間距、1.5rem 內距與 0.5rem 圓角。
- 對應預設樣式集. gap-md、p-lg、r-lg 提供相同值,兩個版本使用相同表面與文字角色。
- 保留結果. 卡片的版面、內容與色彩維持不變,只改變樣式決策的撰寫位置。
#在既有 CSS 旁加入 Master CSS
Master CSS 開始為新標記或已轉換的標記生成規則時,繼續載入原本的 CSS。刪除局部規則前先檢查串接:未分層的舊宣告優先於一般分層 utility,即使 Master CSS 的匯入較晚也是如此。因此,同一元素同時保留兩個選擇器,可能掩蓋轉換是否成功。
@import "@master/css";預設來源探索已足以應付多數專案。只有在遷移需要明確掃描邊界,或 class 未以完整字串出現在原始碼時,才新增 @source、@source not、@safelist 或 @blocklist。
大量轉換檔案前,先依渲染模式決定專案使用靜態、執行階段或漸進式渲染。
#將局部 CSS 轉為標記中的 class
如果 class 清單比獨立選擇器更容易審查,就把局部視覺宣告移到標記。此範例已使用安裝之預設樣式集的共用表面與文字角色,轉換時維持不變。
.card { display: grid; gap: 1rem; padding: 1.5rem; border-radius: .5rem; background: var(--color-surface-raised); color: var(--color-text-body);}export function Card({ children }: { children: React.ReactNode }) { return ( <section className="grid gap-md p-lg r-lg bg-surface-raised fg-text-body"> {children} </section> )}以兩種樣式方式建立相同卡片
若值代表產品決策,優先使用 bg-surface-card、p-card 或 r-card 這類具名主題 token。若 CSS 屬性本身比新抽象更清楚,則使用 (scroll-margin-top:4rem) 這類直接的原生宣告 class。
#當 CSS Modules 是元件 API 時予以保留
元件提供穩定的局部 class 名稱、需要選取內部結構,或在局部變體中使用專案 token 時,CSS Modules 仍然有用。
Vite、Next.js 與 Webpack 會自動為局部樣式表提供專案主題定義。原生宣告會輸出所需變數,不必再次匯入應用程式的 CSS 輸出。
.button { display: inline-flex; align-items: center; justify-content: center; border-radius: var(--radius-md); gap: var(--spacing-xs); padding-inline: var(--spacing-md); background-color: var(--color-blue-60); color: oklch(100% 0 none); min-height: 2.5rem;}.button:disabled { opacity: 0.5; pointer-events: none;}import styles from './Button.module.css'export function Button(props: React.ButtonHTMLAttributes<HTMLButtonElement>) { const { className, type = 'button', ...buttonProps } = props return <button className={[styles.button, className].filter(Boolean).join(' ')} type={type} {...buttonProps} />}如果移除模組會迫使太多使用處同時修改,就採用這個模式。日後局部 class 不再有價值時,再移除模組。
上方模組未分層,因此其一般宣告優先於透過 className 傳入的分層 utility。若呼叫端應能以 utility 覆寫元件預設值,請將預設值放入 @layer components,並驗證串接結果。
此包裝元件預設使用 type="button";送出表單時請傳入 type="submit"。採用時,保留既有元件的 ref 轉送與事件契約。
#將共用值移到 @theme
大量轉換 class 前,通常應先將專案擁有的自訂屬性放進 @theme,讓標記使用 token 名稱,而非寫死的值。
@import "@master/css";@theme { --color-surface-card: var(--color-surface-raised); --color-body: var(--color-text-body); --spacing-card: 1.5rem; --radius-card: .5rem;}<article class="grid r-card fg-body grid-cols(3)@md bg-surface-card p-card"> ...</article>由執行階段管理、元件私有,或刻意不屬於設計系統的值,保留為一般 CSS 自訂屬性。
<div className="(width:var(--progress)) bg-blue-60" style={{ '--progress': `${value}%` } as React.CSSProperties} />#驗證遷移結果
每批工作都要同時檢查程式碼與介面。
- 執行專案的格式化、lint、型別檢查、測試與正式建置。
- 使用程式碼檢查驗證 Master CSS class 字串。
- 比較遷移畫面在明暗主題、響應式斷點、懸停與焦點狀態下的表現。
- 若將選擇器、動畫或第三方輸出改成 class 字串後更難理解,則保留其 CSS。
- 確認沒有元件或頁面依賴舊 CSS 後,才能移除。

