許多應用,一套系統
常見的失敗模式:一套元件庫是為了應用 A 打造的,但應用 B 的按鈕高了 4px、圓角多了 2px,所以 B「就這一次」把按鈕元件複製了一份。五個應用程式之後,總共就有了六種按鈕、八種選擇器、三種模態框,全部在做同樣的事情。
讓共享元件變成 Token 驅動的:它負責承載行為、標記和狀態,每個主題則提供自己的測量值。然後逐一應用程式進行整合。
Token 化元件
將每個應用程式之間有差異的所有測量值,都從主題 Token 中讀取:
@theme {
--spacing-btn-sm: 1.75rem; --spacing-btn-px-sm: 0.625rem;
--spacing-btn-md: 2.25rem; --spacing-btn-px-md: 0.875rem;
--spacing-field: 2.125rem; /* 輸入框 / 選擇器高度 */
--text-label: 0.8125rem;
}
md: "h-btn-md px-btn-px-md text-sm",
icon: "size-btn-md",
然後:
- 一個應用程式套件(suite)只需在其主題中覆寫這些 Token,該套件下的每個應用程式就會獲得自己的尺寸控制。
- 圓角、填充和陰影已經是 Token(例如
rounded-field、bg-accent、--shadow-card)。 - 顏色填充可使用「顏色 + 裝飾類別」的方式,例如
bg-accent accent-fill。如果某個主題只定義了裝飾樣式(例如一層光澤),則永遠不會渲染出透明按鈕。
請參閱 references/button.tsx。
逐一應用程式的整合步驟
-
盤點本地副本。 在每個應用程式中搜尋
export function (Btn|Button|IconBtn|Select|Field|Input|Modal|Dialog|Sheet|Badge|Empty|Pager|Toast|Stat|Status|Spinner)。計算每個元件的使用次數。 -
替換並刪除 共享元件經過主題設定後能完全匹配的本地副本。明確對映變體名稱,並將對應關係記錄下來:
本地名稱 共享變體 primary,navyaccentsecondaryoutlinequiet,softsubtledanger(實心)danger-solid -
保留應用程式專有的部分:
- 領域專用的元件、圖表以及應用程式外殼(chrome);
- 其「形狀」就是品牌識別的控制項(iOS 扁平風格、Carbon 的平面控制項、48px 的藥丸型 CTA 按鈕);
- 其行為是共享元件所不具備的控制項(三態核取方塊、可移除的晶片、一個「全部」重置項目、多選篩選器)。
如果一個薄包裝層只是為了新增一個功能,那是可以接受的。請在註解中說明原因。
-
淘汰原生的
<select>和基於選單的選擇器,統一使用一種 Select 元件。- Radix Select 保留
""作為「無值」:請使用一個標記值(sentinel),例如"all",然後再將其對映回原本的值。 - 轉發
aria-labelledby和aria-describedby。 - 呼叫
selectOption()的測試腳本必須先點擊下拉式選單框,再點擊選項。
- Radix Select 保留
-
根據每個應用程式自身的截圖進行驗證(請參閱
visual-regression-pass)。實際的差異通常是預期的:尺寸、光澤、按壓縮放效果。
tailwind-merge 陷阱(cn())
tailwind-merge 只知道 Tailwind 內建的尺寸比例。自訂 Token 會以三種靜默方式破壞它:
- 未知的間距 Token。
cn("h-btn-md", "h-10")會 同時保留兩者,因為它不知道h-btn-md是一個高度值,而樣式表順序決定了最終生效的樣式。呼叫者傳入的h-10被 Token 覆蓋,導致一個 40px 的按鈕以 36px 渲染。請註冊 Token:extendTailwindMerge({ extend: { theme: { spacing: ["btn-sm","btn-md","field", …] } } })。 - 未知的文字尺寸。
cn("text-meta", "text-ink")會將text-meta視為顏色並將其丟棄。請註冊尺寸:theme: { text: ["meta","micro","label"] }。 - 自訂陰影和顏色。 例如
text-14與text-t3並存,或shadow-[var(--x)]與具名陰影並存:問題相同。當程式碼庫中廣泛使用封閉的自訂尺寸比例時,在那些地方請直接使用單純的clsx。
請參閱 references/cn.ts。
焦點與驗證
aria-invalid:ring-*樣式的優先級高於focus-visible:ring-*,這會隱藏處於焦點且無效欄位的焦點環。請改寫為aria-invalid:not-focus-visible:ring-…。- 表單中原生的
<button>會觸發提交行為。如果共享的 Button 元件沒有預設type="button",則每個已轉換的非提交用途呼叫點都必須設定它。