create-design-system について
ウェブサイトからデザイントークンやブランド素材を抽出し、ブランドに沿った UI を作るエージェント向けに構造化ファイルとして保存するスキルです。ブラウザーでの調査、スクリーンショット、素材の扱い方を案内します。
主な機能
- 01スタイルとデザイントークンの抽出
- 02ホームページのスクリーンショット
- 03ロゴとファビコンの保存
- 04条件に合う自社ホストフォントの収集
- 05構造化デザインファイルの作成
活用シーン
- 01ウェブサイトのビジュアル体系の記録
- 02UI エージェント向けブランド資料の準備
- 03サイトのロゴとファビコンの保存
- 04サイト提供フォントの収集
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ edukeyteam/ agent-toolbox --skill create-design-systemインストール後、Claude Code で/create-design-systemを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/setup.mdscripts/decode-binary-assets.cjsfetch-binary-assets.browser.js詳細
ウェブサイトからデザインシステムを作成する
任意のウェブサイトからデザイントークンとブランドアセットを抽出し、そのブランドに一貫した UI を構築するエージェントが使えるよう、構造化ファイルとして保存します。
前提条件
このスキルには Node.js、Playwright CLI、設定済みのブラウザーが必要です。利用可能かどうかを確認してください。不足しているものがあれば、ユーザーに簡潔に伝えたうえで、一度限りのセットアップガイドに従い、許可されたパッケージマネージャーからインストールしてください。環境上必要な場合に限り、承認を求めてください。
以下の例では、名前付きセッションを 1 つ使って playwright-cli を実行します。プロジェクトでローカル CLI を使っている場合は、各コマンドでそのランナーを使用してください。呼び出しの間もシェルのエイリアスが保持されることを前提にしないでください。完了したら、playwright-cli -s=design close でセッションを閉じてください。
手順
Step 1 — 出力ディレクトリを準備する
作業を開始する前に、必要なディレクトリを作成します。
Bash では mkdir -p assets docs を使用します。Windows PowerShell では New-Item -ItemType Directory -Force assets, docs | Out-Null を使用します。
Step 2 — ページを開き、オーバーレイを閉じる
⚠️ 必ず実行 — スクリーンショットの撮影やスタイルの抽出を行う前に、ページを覆っている Cookie バナー、モーダル、オーバーレイをすべて閉じてください。 このスキルで最もよくある失敗です。オーバーレイがあると、計算済みスタイルが歪み、スクリーンショットも台無しになります。
一度限りのインストール時に設定したブラウザーで URL を開きます。
playwright-cli -s=design open <URL>
ページが HTTP 403 を返すか、ブロック/チャレンジページを表示する場合は、そのページからスタイルを抽出したり、スクリーンショットを撮影したりしないでください。少し待ってから、同じセッションで 1~2 回、再度ページへの移動を試してください。一時的なブロックは解除されることがあります。設定済みの別のブラウザーを試してもかまいませんが、Chrome がインストールされていると決めつけたり、サイトのアクセス制御を回避したりしないでください。実際のページを開けないままなら、デザインシステムを作り上げず、ブロックされたことを報告して中止してください。これはサイト側の応答であり、必ずしも Linux や Playwright のセットアップに問題があるとは限りません。
Cookie/同意バナーは遅れて読み込まれることが多く、最初のスナップショットには表示されないことがあります。(実例:バナーが読み込みから数秒後にだけ表示され、気づかないうちにスクリーンショットに写り込んでいました。)そのため、次の手順を実行してください。
- 遅れて表示されるバナーが DOM に追加されるまで待ちます。
waitコマンドはありません。run-codeを使ってください。playwright-cli -s=design run-code "async page => await page.waitForTimeout(3000)" - スナップショット全体を読むのではなく、バナーを検索します。
findは該当ノードとそのコンテキストのみを返すため、トークン消費を大幅に抑えられます。playwright-cli -s=design find --regex "/cookie|consent|accept|akceptuj|zgadzam/i" - バナーの ref を指定して、その領域だけのスナップショットを取得し、ボタンを正確に確認します。
playwright-cli -s=design snapshot e945 - ref を指定して同意ボタンをクリックします。よくあるラベルは「Accept」「Accept all」「Accept All Cookies」「OK」「I agree」「Allow all」、ポーランド語では「Akceptuj」「Akceptuj wszystkie」「Zgadzam się」「Rozumiem」です。
playwright-cli -s=design click e955 - 続行する前に、バナーが消えたことを確認してください。ボタンのラベルを指定して
findを再実行し、該当なしとなることを確認します。playwright-cli -s=design find --regex "/Accept All Cookies/i" - 同意後に表示されるツールチップ、オンボーディング、ニュースレターのポップアップも閉じてください。
各コマンドはスナップショットそのものではなく、スナップショットへのリンク(.playwright-cli/page-<timestamp>.yml)を出力します。find だけでは不十分な場合に限り、そのファイルを読み込むか grep してください。
Step 3 — ホームページのスクリーンショットを撮影する
🛑 停止 — オーバーレイがないか、もう一度確認してください。 このスクリーンショットを撮影する前に、スナップショットをもう一度取得し、コンテンツを覆う Cookie バナー、モーダル、ポップアップがないことを確認します。バナーは遅れて表示されるため、Step 2 で問題がなくても、その後に表示されていることがあります。ページを覆っているものがあれば、閉じてから(Step 2)、スクリーンショットを撮影してください。
playwright-cli -s=design screenshot --filename=assets/homepage.png --full-page
保存したら、利用可能な画像ビューアーで assets/homepage.png を開き、バナーやオーバーレイの帯が写っていないことを目視で確認してください。写っている場合は、それを閉じて撮り直してください。汚れたスクリーンショットのまま先に進まないでください。
ステップ 4 — eval でデザイントークンを抽出する(複数回の小さな呼び出しで実行)
以下の各呼び出しは、playwright-cli -s=design eval に渡す JavaScript のアロー関数です。3 回の呼び出しは分けてください。1 回にまとめると、デバッグが難しく、出力も読みづらくなります。
小さな結果はインラインで出力できます。サイズの大きな結果は、コンテキストウィンドウに入らないよう --filename を指定してファイルに出力してください。
playwright-cli -s=design eval "<the arrow function below>" --filename=tokens-a.json
--filename はオブジェクトを JSON として書き込みます。返された文字列は JSON で引用符付きになる場合があります。解析する前にファイルを確認し、文字列は一度だけ解析してください。
呼び出し A — コア要素のスタイル
() => {
const getStyles = (el) => {
if (!el) return null;
const s = getComputedStyle(el);
return {
color: s.color,
backgroundColor: s.backgroundColor,
fontFamily: s.fontFamily,
fontSize: s.fontSize,
fontWeight: s.fontWeight,
padding: s.padding,
margin: s.margin,
borderRadius: s.borderRadius,
lineHeight: s.lineHeight,
};
};
return {
body: getStyles(document.body),
header: getStyles(document.querySelector('header') || document.querySelector('[class*="header"]')),
nav: getStyles(document.querySelector('nav')),
h1: getStyles(document.querySelector('h1')),
h2: getStyles(document.querySelector('h2')),
link: getStyles(document.querySelector('a')),
};
}
呼び出し B — 色、角丸、ボタン
() => {
const allEls = document.querySelectorAll('*');
const bgColors = new Set(), textColors = new Set(), borderRadii = new Set();
for (let i = 0; i < Math.min(allEls.length, 500); i++) {
const s = getComputedStyle(allEls[i]);
if (s.backgroundColor !== 'rgba(0, 0, 0, 0)') bgColors.add(s.backgroundColor);
textColors.add(s.color);
if (s.borderRadius !== '0px') borderRadii.add(s.borderRadius);
}
// Buttons with non-transparent backgrounds
const btnStyles = [];
for (const btn of document.querySelectorAll('button, [class*="btn"], a[class*="button"]')) {
const s = getComputedStyle(btn);
if (s.backgroundColor !== 'rgba(0, 0, 0, 0)') {
btnStyles.push({
text: btn.innerText?.trim().substring(0, 30),
bg: s.backgroundColor, color: s.color,
borderRadius: s.borderRadius, padding: s.padding,
fontSize: s.fontSize, fontWeight: s.fontWeight,
border: s.border
});
if (btnStyles.length >= 5) break;
}
}
return {
uniqueBackgrounds: [...bgColors],
uniqueTextColors: [...textColors],
uniqueBorderRadii: [...borderRadii],
btnStyles
};
}
呼び出し C — ロゴ、ファビコン、フォント
() => {
// Logo: look in header .logo or [class*="logo"] — NOT country flag selectors
const logoEl = document.querySelector('header .logo') ||
document.querySelector('[class*="header"] [class*="logo"]');
const logoSVGEl = logoEl?.matches('svg') ? logoEl : logoEl?.querySelector('svg');
const logoSVG = logoSVGEl?.outerHTML?.trim() ?? null;
const logoImageCandidates = [...document.querySelectorAll('header img, [class*="header"] img')]
.filter(img => img.getBoundingClientRect().width > 50)
.slice(0, 8)
.map(img => ({ alt: img.alt, src: img.currentSrc || img.src }));
// Favicon href
const favicon = document.querySelector('link[rel*="icon"]');
// Font faces from CSS rules
const fontFaces = [];
for (const sheet of document.styleSheets) {
try {
for (const rule of sheet.cssRules) {
if (rule.type === CSSRule.FONT_FACE_RULE) {
fontFaces.push(rule.cssText.substring(0, 300));
}
}
} catch(e) {}
}
// Nav links
const navLinks = [...document.querySelectorAll('nav a')].slice(0, 5).map(a => ({
text: a.innerText?.trim().substring(0, 30),
color: getComputedStyle(a).color,
fontWeight: getComputedStyle(a).fontWeight,
textTransform: getComputedStyle(a).textTransform,
}));
return {
logoSVG,
logoImageCandidates,
faviconHref: favicon?.href ?? null,
fontFaces: fontFaces.slice(0, 8),
navLinks
};
}
ステップ 5 — ブランドアセットを保存する
ロゴ: logoSVG に SVG マークアップが含まれている場合に限り、assets/logo.svg に保存します。存在しない場合は、logoImageCandidates と表示されているヘッダーを確認し、旗や製品画像ではなく、実際のワードマークを特定してください。選択した画像の URL が SVG を返し、ページでの取得が許可されている場合は、ページ内で SVG を取得し、返されたテキストを検証してから assets/logo.svg として保存します。SVG テキストを含む --filename の結果は、JSON エンコードされた文字列の場合があります。内容を確認し、解析は一度だけ行ってください。二度解析しないでください。
選択したワードマークが PNG、JPEG、または WebP 画像の場合は、ページのコンテキスト内で実際のバイトデータを取得してください。logo.png、logo.jpg、または logo.webp を base64 にマッピングする単一エントリのオブジェクトを返します(拡張子はレスポンスの Content-Type またはファイルシグネチャから選択してください)。そのオブジェクトを --filename=logo-binary.json で保存し、ステップ 5b にある既存の Node ヘルパーを使ってデコードします。入力ファイルには logo-binary.json を指定してください。保存した画像を開いて確認します。取得がブロックされた場合は、ロゴを保存できなかったことを報告してください。ラスター画像のバイトデータや HTML を logo.svg に入れてはいけません。
ファビコン + カスタムフォント(バイナリアセット): すべてのプラットフォームで、ステップ 5b にあるページ内取得ヘルパーを使用してください。これらのアセットのダウンロードに curl、wget、または PowerShell の Invoke-WebRequest を使用しないでください。シェルのダウンローダーは、サイトの同一オリジンのコンテキストを回避する可能性があり、エージェント環境で拒否されることがあります。サイトのアセット利用権を尊重してください。
ファビコンだけを取得するには、ヘルパーを実行し、出力から favicon.* を残してください(このヘルパーはフォントにも対応しています。ステップ 5b を参照)。
ステップ 5b — カスタム/セルフホストフォントをダウンロードする(サイトが提供している場合のみ)
実施する場合: サイトがセルフホストの @font-face ルールを使って独自のカスタムフォントを配信している場合に限ります(サイトのオリジン上にあるフォントファイル。例:…/themes/site/fonts/Brand-Regular.woff2)。サイトが次のものだけを使っている場合はスキップしてください。
- 汎用システムフォント(
Arial、-apple-system、Segoe UIなど)、または - 同一オリジンから取得できないサードパーティ CDN(例:
fonts.gstatic.com、Adobe Typekit)。これらは CORS によりブロックされ、「再配布するためにウェブサイトが提供するフォント」には該当しません。
ステップ 4/呼び出し C で収集した fontFaces がサイトのドメイン上にある実際のフォントファイルを指している場合は、ダウンロードします。
-
ページ内ですべてのバイナリーアセットを取得する(フォント+ファビコン)。コンテキストを大量のデータで埋め尽くさないよう、base64 をファイルに書き込みます。このスキルがあるフォルダーに対応するコマンドを使ってください。プロジェクトフォルダー内のコマンドはプロジェクトのルートから実行します。スキルが別の場所にある場合(たとえばプロジェクト内の
.claude/skills/create-design-systemやユーザーフォルダー内の$HOME/.claude/skills/create-design-system)、このSKILL.mdを見つけて、隣接するscripts/ファイルを使ってください。Bash、プロジェクトのスキルフォルダー内のスクリプト:
playwright-cli -s=design eval "$(cat '.agents/skills/create-design-system/scripts/fetch-binary-assets.browser.js')" --filename=binary-assets.jsonBash、ユーザーのスキルフォルダー内のスクリプト:
playwright-cli -s=design eval "$(cat "$HOME/.agents/skills/create-design-system/scripts/fetch-binary-assets.browser.js")" --filename=binary-assets.jsonWindows PowerShell、プロジェクトのスキルフォルダー内のスクリプト:
playwright-cli -s=design eval (Get-Content -Raw '.agents\skills\create-design-system\scripts\fetch-binary-assets.browser.js') --filename=binary-assets.jsonWindows PowerShell、ユーザーのスキルフォルダー内のスクリプト:
playwright-cli -s=design eval (Get-Content -Raw (Join-Path $HOME '.agents\skills\create-design-system\scripts\fetch-binary-assets.browser.js')) --filename=binary-assets.jsonスクリプトはアセットパスを base64 データに対応付けたオブジェクトを返します。
-
Node ヘルパーをフォアグラウンドで使い、実ファイルにデコードします。 対応するスクリプトの場所を使ってください。
Bash、プロジェクトのスキルフォルダー内のスクリプト:
node '.agents/skills/create-design-system/scripts/decode-binary-assets.cjs' binary-assets.json assetsBash、ユーザーのスキルフォルダー内のスクリプト:
node "$HOME/.agents/skills/create-design-system/scripts/decode-binary-assets.cjs" binary-assets.json assetsWindows PowerShell、プロジェクトのスキルフォルダー内のスクリプト:
node '.agents\skills\create-design-system\scripts\decode-binary-assets.cjs' binary-assets.json assetsWindows PowerShell、ユーザーのスキルフォルダー内のスクリプト:
node (Join-Path $HOME '.agents\skills\create-design-system\scripts\decode-binary-assets.cjs') binary-assets.json assetsヘルパースクリプトの保存場所にかかわらず、現在のプロジェクト内に
assets/fonts/<family>/*とassets/favicon.*を書き込み、書き込んだファイル数を出力します。 -
確認してクリーンアップする:
assets/fonts/とそのサブディレクトリを確認します。フォントファイルのサイズが 0 ではなく、妥当な範囲であること、トップレベルに予期しないファイルがないことを確認してください。完了したら、一時ファイルbinary-assets.jsonを削除します。
🧨 この事例から学ぶ — 独自のデコードループを書く前にお読みください。 ページ内コードが 文字列(例:
JSON.stringify(obj))を返すと、保存したファイルが二重エンコード(JSON を値とする JSON 文字列)になることがあります。単純なJSON.parse(file)の結果は 文字列 になり、Object.entries(string)は文字ごとに 1 エントリーを反復します。以前の実行では、assets/fonts/に0、1、2……という名前の 約 600,000 個の 0 バイトファイルが書き込まれました。付属のヘルパーは、次の 2 つの方法でこれを防ぎます。(a)fetch-binary-assets.browser.jsは文字列化された文字列ではなくオブジェクトを返します。(b)decode-binary-assets.cjsは値がまだ文字列の場合に再度解析し、エントリー数が 200 を超える場合、キーが数値の場合、または値がフォントとして短すぎる場合は書き込みを拒否します。ヘルパーを必ず優先してください。手動で実装する場合は、これらのガードを再現し、デコーダーをバックグラウンドで実行しないでください。
📜 アセットの権利についてユーザーに伝えてください(必須)。 カスタムフォントやロゴにはライセンスが適用されている場合があります。ユーザーへの最終回答で、ダウンロードしたフォントとロゴを特定し、使用または再配布する前に利用権を確認するよう伝えてください。フォント、ロゴ、その他のブランドアセットが自由に再利用または再配布できると決めつけないでください。
ステップ 6 — design-tokens.json を作成する
抽出した値を構造化された JSON に対応付けます。次のスキーマに従ってください。
同意通知を閉じた後も、その要素が DOM 内で非表示のまま残る場合があります。色やコンポーネントを選ぶ際は、非表示またはサイズが 0 の要素を無視してください。最初に一致したセレクターをそのまま信頼せず、候補のボタン、リンク、見出しが実際に表示されているページにあることを確認してください。
{
"colors": {
"brand": { "primary": "#...", "accent": "#...", "error": "#...", "success": "#..." },
"background": { "default": "#fff", "light": "#...", "overlay": "rgba(...)" },
"text": { "primary": "#...", "secondary": "#...", "muted": "#...", "onDark": "#fff" }
},
"typography": {
"fontFamily": { "primary": "\"Font Name\", Arial, sans-serif" },
"fontWeights": { "regular": 400, "medium": 500, "semibold": 600, "bold": 700 },
"fontSize": { "sm": "12px", "base": "14px", "md": "16px", "lg": "20px", "xl": "24px" },
"lineHeight": { "tight": "1.2", "base": "1.4", "relaxed": "1.6" }
},
"spacing": {
"1": "4px", "2": "8px", "3": "12px", "4": "16px",
"5": "20px", "6": "24px", "7": "28px", "8": "32px"
},
"borderRadius": {
"none": "0px", "sm": "4px", "md": "8px", "lg": "16px",
"full": "999px", "circle": "50%"
},
"components": {
"button": { "primary": { "backgroundColor": "#...", "color": "#...", "padding": "...", "borderRadius": "..." } },
"header": { "backgroundColor": "#...", "padding": "...", "fontSize": "..." },
"nav": { "color": "#...", "fontSize": "...", "fontWeight": "..." }
}
}
assets/design-tokens.json として保存します。
ステップ 7 — docs/design-guidelines.md を作成する
ガイドライン文書には、次の内容を含めてください。
- アセットの表 — 保存したすべてのファイルへのリンク(
docs/からの相対パス) - カラーセクション — トークン名、16 進数、用途
- タイポグラフィセクション — フォントファミリー(@font-face の詳細を含む)、ウェイトのスケール、サイズのスケール。カスタムフォントをダウンロードした場合(ステップ 5b)、各ダウンロードファイルと
assets/fonts/…以下のリポジトリ内パスを記載し、エージェントがフォントがローカルに存在し、アプリで使用できることを把握できるようにしてください。また、使用前にライセンスを確認する必要がある旨を一行で追記してください。 - スペーシングセクション — 基準単位と一般的な値
- 角丸セクション — すべての角丸と用途
- コンポーネントセクション — ヘッダー、ナビゲーション、ボタン、入力欄、プロモーションバーなど
- ロゴの使用方法 — 保存したロゴの使用方法と使用場所。反転版がある場合は、暗い背景で使用してください
- ビジュアルスタイルの概要 — ブランドの個性を説明する 3〜5 文
docs/design-guidelines.md として保存します。
ステップ 8 — 確認して引き渡す
一時ファイル(例:binary-assets.json、スナップショットダンプ)を削除し、assets/ に不要なファイルが紛れ込んでいないことを確認して、生成したファイルをユーザーと一緒に確認します。Git リポジトリでは、関係のない変更を含めないよう git status --short を確認します。ユーザーがコミットを依頼し、リポジトリのコントリビューション手順で許可されている場合に限り、コミットします。
よくある問題と対処方法
| 問題 | 対処方法 |
|---|---|
eval の結果がコンテキストを埋め尽くす | 結果を出力する代わりに --filename=<file>.json を追加してファイルに書き込み、そのファイルを読み込みます |
| ロゴのセレクターでワードマークを検出できない | 表示されているヘッダー画像を確認します。ワードマークはインライン SVG ではなく、SVG またはラスター画像を指す <img> の場合があります |
| サイトを開くと 403 またはブロックされたページが表示される | 同じブラウザーセッションで少し待ってから再試行します。アクセスできない状態が続く場合は、その旨を報告し、ブロックページから情報を抽出しないでください |
CLI に wait コマンドがない | playwright-cli -s=design run-code "async page => await page.waitForTimeout(3000)" を使用します |
eval が SyntaxError: Unexpected token ';' で失敗する | 引数は 式 である必要があります。; で終わるスクリプトファイルを渡すと失敗します。ヘルパースクリプトの末尾は意図的にセミコロンなしの } になっています |
デコーダーが require is not defined in ES module scope で停止する | ES モジュールを使用するプロジェクトでは、拡張子を変更せず、用意されている .cjs ヘルパーを実行します |
| スナップショットが大きすぎて読み取れない | playwright-cli -s=design find "<text>" または playwright-cli -s=design find --regex "/pattern/i" を使用します。一致するノードとそのコンテキストのみが返されます |
| アセットの直接ダウンロードが拒否される | scripts/fetch-binary-assets.browser.js を使ってページ内で同一オリジンのアセットを取得し、scripts/decode-binary-assets.cjs でデコードします |
| Cookie バナーが検出されず、スクリーンショットに残る | バナーは遅延して読み込まれるため、最初のスナップショットには表示されません。3 秒待つ → find でバナーを検索 → 閉じる → 再度 find して一致がないことを確認し、その後スクリーンショットを撮ります。保存した PNG も目視で確認し、バナーが残っていないことを確かめます |
| Cookie 同意ダイアログが閉じられない | スタイルを抽出する前に、必ず「Accept all」/「OK」/「Akceptuj wszystkie」があるか確認してクリックします。オーバーレイがあると、計算済みスタイルが不正確になります |
| デコードによって 0 バイトのファイルが数十万個作成される | filename の出力先は 文字列 の戻り値を二重エンコードします。ページ内スクリプトからはオブジェクトを返してください。デコーダーでは文字列を再解析し、200 件を超える項目や数値キーを拒否する必要があります。用意されているヘルパーを使用し、デコーダーを run_in_background で実行しないでください |
| 想定していたサブフォルダーだけを確認する | フォントをダウンロードした後、親ディレクトリの assets/fonts/ も確認してください。不要なファイルが最上位に置かれることがあり、サブフォルダーだけを確認すると見落とします |
bg の計算済みスタイルが rgba(0, 0, 0, 0) を返す | これは透明を意味します。カラーパレットの作成時には除外します |
フォントがブランドフォントではなく Arial と表示される | スタイルシートの @font-face ルールを確認します。ブランドフォントはカスタムエイリアスとして読み込まれている場合があります(例:Euclid = Euclid Circular B) |
出力ファイル
すべてのファイルはプロジェクトのルートに保存されます。
assets/
homepage.png # Full-page screenshot (no overlays!)
logo.<source-format> # Brand wordmark, e.g. logo.svg or logo.png (only when obtainable)
favicon.ico # Favicon binary
design-tokens.json # Structured design tokens
fonts/ # Self-hosted custom fonts (only if the site provides them)
<family>/ # e.g. brygada-1918/, libre-franklin/
*.woff2 / *.ttf # downloaded font files
docs/
design-guidelines.md # Human-readable design system reference