i18n 是你的翻譯。
KDF 是你的設計。
兩者都住在 JSON 裡。
專為伺服器端 Web 應用程式及 AI 輔助 UI 打造的 JSON 設計協調層。所有可重複的決策 — 排版、間距、字體、元件樣式 — 都集中在一個事實來源,讓你的 AI 代理讀取,不再需要從各種檔案、頁面跟過往紀錄中到處拼湊。
{ "$layout": ["hero", "footer"], "hero": { "wrapper": "mx-auto max-w-6xl px-6 py-20", "title": "@typography.h1", "cta-primary": "@button.cta" } }
單一的事實來源,讓每個頁面、元件與 AI 會話都保持設計一致性。
JSON 負責定義 · 程式碼負責渲染 · data-kdf 互相映射
設計在你的元件裡
漸漸失控。
當 class 名稱只存在於 .tsx 檔案裡時,每個頁面的設計就會慢慢分岔。如果只有單一真人開發者在維護還過得去 — 但一旦讓 AI 代理開始動 UI,這一切就會變得不堪一擊。
<section className="mx-auto max-w-6xl px-6 py-20"> <h1 className="text-5xl font-semibold tracking-tight"> // …and again, slightly different, on the next page
同一個按鈕默默長出了五種變體,每個頁面的間距都不太一樣。而且每次新的 AI 代理對話,都得從頭重新摸索一次設計規則。
- — AI 代理自己發揮創意亂套顏色、間距、字體跟排版。
- — 使用者每次都得在聊天室裡重新糾正它。
- — 每次修改都得在元件檔裡大海撈針。
- — 不斷重複的對話讓最初的設計理念蕩然無存。
→ 永無止境的"大一點" · "藍一點" · "往左移"來回修改
讓設計明確化。
KDF 為設計做的事,就跟 i18n 為文字做的事一樣:它把可重複的決策從元件檔案中抽離,移到 JSON 裡。渲染出來的 UI 依然用的是一般的 CSS — 改變的只有它的歸屬權。
{ "hero": { "title": "text-5xl font-semibold tracking-tight" } }
const d = getDesign("homepage"); <h1 data-kdf="hero.title" className={d("hero.title")}>
元件庫告訴你這是什麼,
KDF 告訴你這是哪一個.
設計庫會給你一個 Button,KDF 則會告訴你這是 homepage.hero.cta-primary,被渲染成一個 Button,並套用了這個 Token 的樣式。它補足了元件庫漏掉的那塊拼圖 — 將元素與設計決策連結起來。
六個符號。
一套語法。
KDF 儲存 class 名稱、共用參考、區塊順序及 CSS 自訂屬性。它絕對不存業務邏輯、事件處理器、資料請求、權限或是無障礙 (a11y) 行為。
每個使用 Token 的元素都帶有對應的路徑。DOM 節點可以直接追溯回 JSON — 方便掃描器、測試及 AI 代理檢查。
將路徑解析為它的 className 字串。d.css() 則會回傳 CSS 自訂屬性物件。
指向 shared/ 中可重複使用的 Token。參考可以串聯,並加上額外的 class 來擴充。
一個陣列,包含區塊的 key。列出的區塊會依序渲染;沒被列出的則會被主應用程式隱藏。
給 AI 代理跟主機端工具看的 Metadata — 說明是哪個元件渲染了這個 Token,還有變體與大小提示。
無法用共用 class 表達的值,透過 d.css() 作為內聯樣式 (inline style) 變數來套用。
共用預設值,
頁面級覆寫。
設計 Token 住在兩個地方:可重複使用的預設值放在 shared/,而各頁面專屬的組合只會覆寫它需要的部分。有兩個完全由你掌控的 CSS 檔,負責處理首次繪製 (first paint) 以及提供救急用的 escape hatches (特殊覆寫)。
kdf/ shared/ button.json ← reusable defaults card.json color.json typography.json homepage.json ← page composition konde-server.css ← critical, first paint konde.css ← non-critical tweaks
像 @button.cta 這樣的參考,會依序從該頁面的 shared/、父層 shared/,最後到頁面 Token 進行解析。模板只需覆寫需要的部分,剩下的全部繼承。
設計在伺服器端解析,瀏覽器收到的是組裝好的 class 字串。關鍵 CSS 會在首次繪製時載入,剩下的才隨後載入 — 不會把渲染成本丟給效能較差的裝置。
由 App 引入,讓設計變數跟防閃爍 (no-FOUC) 覆寫在畫面首次繪製時就生效 — 在任何東西閃爍前搞定。
/* konde-server.css */ :root { --kdf-primary: #1F8F47; } [data-kdf="hero.slider"] { display: none; }
在框架與 App 的 CSS 後載入,用來做微調、實驗和救急覆寫 (escape hatches) — 這些精細調整從來都不該阻礙畫面的首次繪製。
/* konde.css */ [data-kdf="hero.title"] { letter-spacing: -0.02em; } [data-kdf="hero.wrapper"] { gap: 3rem; }
→ KDF 只會建立這兩個檔案一次,永遠不會覆寫它們。Plugin 會透過環境變數暴露它們的路徑,你的 App 自己去引入就好;它不會偷偷塞任何東西給你。
切換設計
就像切換語言一樣。
就跟 i18n 切換語言一樣,KDF 可以切換設計模板。同一個 App、同一個元件、同一套程式碼 — 只要把 KDF_DIR 指向另一個設計資料夾,整個外觀就會煥然一新。
designs/ lander/ shared/ homepage.json newlander/ shared/ homepage.json
// next.config.ts withKDF({ dir: "./designs/lander" })(nextConfig);
在伺服器端解析,
將字串往下傳。
KDF 核心會從硬碟讀取 JSON,所以它跑在伺服器端:包含 Next.js Server Components、Astro 渲染和 Hono handlers。在那裡把 class 解析好,再把純字串傳給客戶端元件。
import { getDesign, cn } from "@kondeio/kdf"; const d = getDesign("homepage"); <button data-kdf="hero.cta" className={cn(d("hero.cta"), isActive)}>Start</button>
→ 解析好的 className 字串
→ CSS 自訂屬性物件
合併條件式 class、濾掉 falsy 值、去除重複 — 預設是純字串處理 (semantic-free)。
Production 環境會使用快取;Dev 環境會透過 mtime 和檔案大小重新驗證。
它穩穩坐在
你的技術棧上。
樣式處理 — 只存 CLASS 名稱,不存 CSS
KDF 不是 CSS 引擎。Token 裡面放的是你樣式系統看得懂的 class 字串。如果你的框架會掃描原始碼,把 JSON 也加進去:@source "../kdf/**/*.json"。
執行環境 — 伺服器端 JAVASCRIPT
裝一次就好,
剩下的 Init 幫你搞定。
安裝的同時,如果沒有 kdf/ 資料夾,它會順便幫你建出一個初始版本。原有的檔案絕對不會被覆寫。
import withKDF from "@kondeio/kdf/plugin"; export default withKDF({ dir: "./designs" })(nextConfig);