Loading...
Loading...
Extract a design system from any website URL by driving a real browser with the Playwright CLI. Produces design tokens (JSON), brand assets (logo and favicon when available), a homepage screenshot, and a design-guidelines.md document. Use when the user wants to clone, match, or stay consistent with an external brand.
npx skill4agent add edukeyteam/agent-toolbox create-design-systemplaywright-cliplaywright-cli -s=design closemkdir -p assets docsNew-Item -ItemType Directory -Force assets, docs | Out-Null⚠️ MUST DO — dismiss every cookie banner / modal / overlay covering the page BEFORE you screenshot or extract styles. This is the single most common failure of this skill. An overlay both skews computed styles and ruins the screenshot.
playwright-cli -s=design open <URL>waitrun-codeplaywright-cli -s=design run-code "async page => await page.waitForTimeout(3000)"findplaywright-cli -s=design find --regex "/cookie|consent|accept|akceptuj|zgadzam/i"playwright-cli -s=design snapshot e945playwright-cli -s=design click e955findplaywright-cli -s=design find --regex "/Accept All Cookies/i".playwright-cli/page-<timestamp>.ymlfind🛑 STOP — re-check for overlays first (yes, again). Before this screenshot, take one more snapshot and confirm NO cookie banner, modal, or popup is covering content. Banners load on a delay, so even if the page looked clean in Step 2 one may have appeared since. If anything is covering the page, dismiss it (Step 2) and only then screenshot.
playwright-cli -s=design screenshot --filename=assets/homepage.png --full-pageassets/homepage.pngplaywright-cli -s=design eval--filenameplaywright-cli -s=design eval "<the arrow function below>" --filename=tokens-a.json--filename() => {
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')),
};
}() => {
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
};
}() => {
// 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
};
}logoSVGassets/logo.svglogoImageCandidatesassets/logo.svg--filenamelogo.pnglogo.jpglogo.webp--filename=logo-binary.jsonlogo-binary.jsonlogo.svgcurlwgetInvoke-WebRequestfavicon.*@font-face…/themes/site/fonts/Brand-Regular.woff2Arial-apple-systemSegoe UIfonts.gstatic.comfontFaces.claude/skills/create-design-system$HOME/.claude/skills/create-design-systemSKILL.mdscripts/playwright-cli -s=design eval "$(cat '.agents/skills/create-design-system/scripts/fetch-binary-assets.browser.js')" --filename=binary-assets.jsonplaywright-cli -s=design eval "$(cat "$HOME/.agents/skills/create-design-system/scripts/fetch-binary-assets.browser.js")" --filename=binary-assets.jsonplaywright-cli -s=design eval (Get-Content -Raw '.agents\skills\create-design-system\scripts\fetch-binary-assets.browser.js') --filename=binary-assets.jsonplaywright-cli -s=design eval (Get-Content -Raw (Join-Path $HOME '.agents\skills\create-design-system\scripts\fetch-binary-assets.browser.js')) --filename=binary-assets.jsonnode '.agents/skills/create-design-system/scripts/decode-binary-assets.cjs' binary-assets.json assetsnode "$HOME/.agents/skills/create-design-system/scripts/decode-binary-assets.cjs" binary-assets.json assetsnode '.agents\skills\create-design-system\scripts\decode-binary-assets.cjs' binary-assets.json assetsnode (Join-Path $HOME '.agents\skills\create-design-system\scripts\decode-binary-assets.cjs') binary-assets.json assetsassets/fonts/<family>/*assets/favicon.*assets/fonts/binary-assets.json🧨 Learn from this incident — read before writing your own decode loop. If the in-page code returns a string (e.g.), the saved file can end up double-encoded (a JSON string of JSON). A naiveJSON.stringify(obj)then yields a string, andJSON.parse(file)iterates one entry per character — a previous run wrote ~600,000 zero-byte files namedObject.entries(string),0,1,… into2. The provided helpers prevent this two ways: (a)assets/fonts/returns an object, not a stringified string; (b)fetch-binary-assets.browser.jsre-parses if it still sees a string AND refuses to write if there are >200 entries, numeric keys, or values too short to be a font. Always prefer the helpers. If you must hand-roll, replicate those guards, and never run the decoder in the background.decode-binary-assets.cjs
📜 Tell the user about asset rights (REQUIRED). Custom fonts and logos may be licensed. In your final response to the user, identify the downloaded fonts and logos and ask them to verify usage rights before using or redistributing those assets. Do not assume fonts, logos, or other brand assets are free to reuse or redistribute.
{
"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.jsondocs/assets/fonts/…docs/design-guidelines.mdbinary-assets.jsonassets/git status --short| Issue | Fix |
|---|---|
| Add |
| Logo selector misses the wordmark | Inspect visible header images; the wordmark may be an |
| Site opens to a 403 or blocked page | Retry briefly in the same browser session; if access remains blocked, report it and do not extract the block page |
No | Use |
| The argument must be an expression. A script file ending in |
Decoder dies with | In projects using ES modules, run the provided |
| Snapshot too large to read | Use |
| Direct asset downloads are denied | Fetch same-origin assets in-page with |
| Cookie banner missed — ends up in the screenshot | It loads on a delay and is absent from the first snapshot. Wait 3s → |
| Cookie consent dialog never dismissed | Always check for and click "Accept all" / "OK" / "Akceptuj wszystkie" before extracting styles — overlays produce incorrect computed styles |
| Decode created hundreds of thousands of 0-byte files | The |
| Verifying only the subfolders you expected | After downloading fonts, inspect the parent |
Computed style returns | This means transparent — skip these when building color palettes |
Fonts show as | Check |
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