i18nが翻訳なら、
KDFはデザインだ。
どっちもJSONにある。
サーバーサイドのWebアプリやAIエージェントを活用したUI開発のための、JSONベースのデザイン調整レイヤーです。レイアウトや余白、タイポグラフィ、コンポーネントのスタイルなど、繰り返し使われるデザインのルールを一つのファイル(SSOT)にまとめます。エージェントは過去の履歴や色々なファイルを漁る必要がなくなり、このJSONを読むだけでデザインを把握できるようになります。
{ "$layout": ["hero", "footer"], "hero": { "wrapper": "mx-auto max-w-6xl px-6 py-20", "title": "@typography.h1", "cta-primary": "@button.cta" } }
一つのソース・オブ・トゥルース(SSOT)が、すべてのページ、コンポーネント、エージェントのセッションに一貫性をもたらします。
JSONで定義 · コードで描画 · data-kdf が元と紐づく
コンポーネントの
デザインがバラバラになる。
クラス名が .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
同じボタンなのにいつの間にか5つもバリエーションができていたり、ページごとに余白が違っていたり。新しいエージェントのセッションを立ち上げるたびに、デザインのルールをゼロから教え直すハメになります。
- — エージェントが色や余白、フォント、レイアウトを適当にでっち上げる。
- — その度に人間がチャットで修正を指示しないといけない。
- — 変更するたびにコンポーネントファイルを探し回ることになる。
- — セッションを繰り返すうちにデザインの意図が失われていく。
→ 終わりのない "もっと大きく" · "もうちょい青く" · "左に寄せて" 修正ループ
デザインを明文化する。
i18nがテキストをコードから分離したように、KDFはデザインの決定事項をコンポーネントから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は「どれか」を指定する。.
デザインライブラリはボタンを提供してくれます。一方でKDFは、これが homepage.hero.cta-primary であり、ボタンとしてレンダリングされ、このトークンでスタイリングされる、ということを教えてくれます。ライブラリには欠けている「要素からデザインの決定事項へのマッピング」を補完するものです。
6つの記号。
1つの文法。
KDFはクラス名、共有参照、セクションの順番、そしてCSSカスタムプロパティを保存します。ビジネスロジックやイベントハンドラ、データ取得、権限、アクセシビリティの挙動などは絶対に保存しません。
トークンを使っているすべての要素は、一致するパスを持っています。DOMノードからJSONまで一直線にたどれるので、スキャナーやテスト、エージェントのレビューに便利です。
パスを解決してクラス名の文字列にします。d.css()はCSSカスタムプロパティのオブジェクトを返します。
shared/ の中にある再利用可能なトークンを参照します。Refはチェーンさせたり、追加のクラスで拡張したりできます。
セクションキーの配列です。リストされたセクションは順番通りにレンダリングされ、書かれていないものはホストアプリ側で非表示になります。
エージェントやホストツールのためのメタデータ。どのコンポーネントがこのトークンをレンダリングするのか、またバリアントやサイズのヒントも含まれます。
再利用可能なクラスとして表現できない値を、d.css()を通じてインラインスタイルの変数として適用します。
共通のデフォルト値と
ページごとの上書き。
デザイントークンは2箇所にあります。再利用可能なデフォルト値は shared/ に置き、ページごとの構成で必要な部分だけを上書きします。初回ペイントや細かい調整(エスケープハッチ)には、ユーザー管理の2つのCSSファイルを使います。
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/、そしてページのトークンという順に解決されます。テンプレートは必要な部分だけを上書きし、残りはそのまま引き継ぎます。
デザインはサーバー側で解決され、ブラウザは完成済みのクラス名文字列だけを受け取ります。クリティカルCSSは最初のペイント時に適用され、残りは後から読み込まれるため、低スペックな端末にレンダリングの負荷を押し付けることがありません。
アプリからインポートされ、デザイン変数やFOUC(スタイルが当たる前の一瞬の崩れ)を防ぐ上書き設定が、画面に何か表示されるより前の最初のペイント時に届きます。
/* konde-server.css */ :root { --kdf-primary: #1F8F47; } [data-kdf="hero.slider"] { display: none; }
フレームワークやアプリのCSSの後に読み込まれ、ちょっとした調整や実験、エスケープハッチに使われます。ペイントをブロックする必要のない微調整用です。
/* konde.css */ [data-kdf="hero.title"] { letter-spacing: -0.02em; } [data-kdf="hero.wrapper"] { gap: 3rem; }
→ KDFはこれらのファイルを一度だけ作成し、勝手に上書きすることはありません。プラグインは環境変数経由でファイルのパスを公開するだけで、インポート自体はあなたのアプリ側で行います。勝手に何かを注入するようなことはしません。
言語を切り替えるように
デザインも切り替える。
i18nが言語を切り替えるように、KDFはデザインテンプレートを切り替えます。アプリもコンポーネントもコードもすべて同じまま、 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のハンドラなど、完全にサーバーサイドで動きます。サーバーでクラスを解決して、クライアントのコンポーネントにはただの文字列として渡します。
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カスタムプロパティのオブジェクト
条件付きクラスの結合、falsyな値の削除、重複排除をこなします。デフォルトでは特定のセマンティクスに依存しません。
本番環境ではキャッシュされます。開発環境ではファイルの更新日時とサイズで再検証します。
手持ちのスタックの
上に乗るだけ。
スタイリング — CSSではなくクラス名を保存
KDFはCSSエンジンではありません。トークンには、お使いのスタイリングシステムが理解できるクラス文字列がそのまま入ります。もしフレームワークがソースファイルをスキャンする仕様なら、JSONもスキャン対象に指定してください: @source "../kdf/**/*.json"。
ランタイム — サーバーサイドJavaScript
インストールは1回。
あとはInitにお任せ。
インストール時に、まだ存在しなければ kdf/ フォルダも初期生成してくれます。既存のファイルが上書きされることは絶対にありません。
import withKDF from "@kondeio/kdf/plugin"; export default withKDF({ dir: "./designs" })(nextConfig);