
Webdesign
- 43 installs
- 17.2k repo stars
- Updated August 1, 2026
- danielmiessler/personal_ai_infrastructure
Designs and integrates web interfaces using Claude Design as the engine, then hands off to the frontend-design plugin for production code.
About
Orchestrates web UI design by driving Anthropic's Claude Design programmatically and integrating the results into existing applications as diffs. A developer uses it to create prototypes, design systems, or redesigns and turn them into production frontend code.
- Drives Claude Design via the Interceptor skill for programmatic access
- Integration-aware: produces diffs against an existing app, not greenfield only
Webdesign by the numbers
- 43 all-time installs (skills.sh)
- Ranked #1,265 of 1,880 Design & UI/UX skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/danielmiessler/personal_ai_infrastructure --skill webdesignAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 43 |
|---|---|
| repo stars | ★ 17.2k |
| Last updated | August 1, 2026 |
| Repository | danielmiessler/personal_ai_infrastructure ↗ |
What it does
Designs and integrates web interfaces using Claude Design as the engine, then hands off to the frontend-design plugin for production code.
Files
Voice Notification (REQUIRED FIRST ACTION)
curl -s -X POST http://localhost:31337/notify \
-H "Content-Type: application/json" \
-d '{"message": "Running the Webdesign skill", "voice_enabled": true}' > /dev/nullWhat This Skill Is
Webdesign is the PAI orchestration layer around Claude Design — Anthropic's web-based visual design product launched in April 2026 at claude.ai/design. Claude Design is not a CLI tool or plugin; it is a surface on claude.ai powered by Claude Opus 4.7 vision. This skill bridges the gap by:
1. Driving Claude Design programmatically through the Interceptor skill (real-Chrome automation of the authenticated claude.ai session). 2. Processing handoff bundles that Claude Design produces, feeding them into local codebases. 3. Delegating production code generation to the frontend-design plugin (Anthropic, auto-activates in Claude Code) when the output is code. 4. Integrating designs INTO existing applications — framework-aware diff/patch flow, not greenfield-only. 5. Verifying and deploying the result via Interceptor + the project's chosen host.
Claude Design is the engine. Webdesign is the cockpit.
Integration-Aware Operation (CRITICAL)
This skill is frequently called as a sub-step of larger site work — writing a blog post, building an admin dashboard, shipping a marketing page. When invoked from a parent context, the skill:
- Accepts existing-project context as input: framework, token file, component directory, deployment target.
- Produces output as diffs / patches against the existing app, not isolated HTML files.
- Respects existing design tokens and component patterns — does NOT overwrite them unless the user requests a full redesign.
- Routes integration work through
Workflows/IntegrateIntoApp.md.
When invoked standalone for a greenfield design, the skill produces a self-contained prototype and optionally scaffolds a new app.
Customization
User-specific design preferences (color palette, typography, spacing grid, animation timing, framework defaults) live at:
~/.claude/PAI/USER/SKILLCUSTOMIZATIONS/Webdesign/
├── PREFERENCES.md # Design tokens, preferred frameworks
├── README.md
└── EXTEND.yamlThe skill reads PREFERENCES.md if present and passes those tokens into Claude Design's brief and any downstream handoff bundle. Without a customization layer, the skill defaults to Claude Design's own system-extraction output.
Workflow Routing
When executing a workflow, output this notification:
Running **WorkflowName** in **Webdesign**...| Workflow | Trigger | File |
|---|---|---|
| CreatePrototype | "design a prototype", "create prototype", "mockup", "build a design" | Workflows/CreatePrototype.md |
| ExtractDesignSystem | "extract design system", "pull tokens from", "extract brand" | Workflows/ExtractDesignSystem.md |
| RefinePrototype | "iterate on", "refine", "adjust spacing", "change color" | Workflows/RefinePrototype.md |
| WebsiteToRedesign | "redesign this site", "rebuild this URL", "modernize" | Workflows/WebsiteToRedesign.md |
| ExportToCode | "export to code", "ship to code", "send to Claude Code", "process handoff" | Workflows/ExportToCode.md |
| IntegrateIntoApp | "integrate this into", "patch into the app", "land in existing codebase" | Workflows/IntegrateIntoApp.md |
| DeployDesign | "deploy the design", "ship to production" | Workflows/DeployDesign.md |
Prerequisites (PREFLIGHT)
Before running any workflow, confirm:
1. Interceptor skill available — which interceptor returns a path. If not, instruct user to invoke Skill("Interceptor") setup first. 2. Authenticated claude.ai session — Interceptor must have a logged-in claude.ai profile. First-run is headed; subsequent runs are headless. 3. Claude Design access — User's Claude subscription must include Claude Design (Pro, Max, Team, or Enterprise with admin opt-in). 4. For `IntegrateIntoApp`: parent-project path + framework identifier (next, astro, vitepress, vite-react, vue, vanilla) passed in context.
Missing prerequisites → halt with a clear remediation step. Never silently fall back.
Gotchas
Accumulate lessons here. Information density is highest in gotchas.
- Claude Design is web-only. There is no API, no MCP server, no plugin. Interceptor is the only programmatic path.
- Real Chrome required. Use the Interceptor skill — it is the only sanctioned browser automation in PAI. Claude Design's UI depends on claude.ai's full session state; CDP-based automation trips bot detection and drops session cookies.
- Handoff bundles are directories, not single files. A bundle contains
PROMPT.md, optionaltokens.json,components/,assets/, and framework-specific scaffolding. Treat the whole directory as the unit. - `frontend-design` plugin auto-activates. When the handoff bundle is fed to Claude Code, the plugin (already installed in the official marketplace) picks up the frontend work automatically — do NOT manually invoke it.
- Claude Design's design-system extraction runs during onboarding. For a new codebase you want Claude Design to understand, run
ExtractDesignSystemFIRST beforeCreatePrototype— otherwise Claude Design uses generic defaults and overrides your tokens. - Integration ≠ overwrite.
IntegrateIntoAppproduces diffs on top of existing code. If the user wants a full redesign that replaces existing UI, explicitly flag this and get confirmation. - Canva exports are editable. If the user wants a non-developer (marketer, founder) to refine the design, route through
Workflows/ExportToCode.mdwith--format canva. - No real-time collab. Claude Design does not support multiplayer editing like Figma. Share via URL export for async review.
- Enterprise gate. Enterprise accounts need an admin to enable Claude Design in Organization settings before the palette icon appears in claude.ai.
- Session quotas. Claude Design burns Opus 4.7 tokens fast. Pro tier is insufficient for sustained pro-design work; Max recommended.
- Output fidelity ≠ production-ready. Claude Design produces polished visuals, but hand-off code often needs a verification + a11y pass. Run
Tools/VerifyDesign.tspost-integration. - Vision doesn't guess. If the prompt doesn't specify responsive breakpoints, contrast requirements, or dark-mode behavior, Claude Design picks defaults that may not match the target app. Be explicit in the brief.
Examples
Example 1: Create a prototype from a brief
User: "Design a pricing page for an AI security startup — editorial aesthetic, dark only"
→ Invokes CreatePrototype workflow
→ Preflight: Interceptor + authenticated claude.ai session
→ Composes brief with explicit aesthetic, constraints, differentiation
→ Drives claude.ai/design via Tools/DriveClaudeDesign.ts
→ Screenshots output, verifies a11y via Tools/VerifyDesign.ts
→ Returns bundle path + preview URLExample 2: Land a Claude Design prototype inside an existing Astro app
User: "Integrate this prototype into ~/Projects/landing — it's an Astro site"
→ Invokes IntegrateIntoApp workflow
→ Audits target project (framework, tokens, components)
→ Runs ExtractDesignSystem first to prime Claude Design with app's real tokens
→ Translates prototype to Astro conventions via frontend-design plugin
→ Produces unified diff against the working tree
→ Pauses for human review before applying
→ Applies patch on a branch, runs tests, screenshots in-contextExample 3: Redesign an existing live site
User: "Redesign example.com — modernize, keep the copy, make it brutalist"
→ Invokes WebsiteToRedesign workflow
→ Captures current state (screenshot + HTML + tokens)
→ Writes critique (what works, what's dated, what to preserve)
→ Composes rebuild brief with explicit aesthetic and preserve list
→ Drives Claude Design with critique + original screenshot as input
→ Iterates via RefinePrototype until satisfied
→ Hands off to IntegrateIntoApp or ExportToCodeFile Organization
skills/Webdesign/
├── SKILL.md # This file — routing + gotchas
├── README.md # Public-facing intro
├── Workflows/
│ ├── CreatePrototype.md
│ ├── ExtractDesignSystem.md
│ ├── RefinePrototype.md
│ ├── WebsiteToRedesign.md
│ ├── ExportToCode.md
│ ├── IntegrateIntoApp.md
│ └── DeployDesign.md
├── Tools/
│ ├── DriveClaudeDesign.ts # Interceptor wrapper for claude.ai/design
│ ├── ProcessHandoffBundle.ts # Parse bundle → structured brief
│ └── VerifyDesign.ts # Screenshot + a11y probe
└── References/
├── ClaudeDesignCapabilities.md # What Claude Design does / doesn't do
├── InputFormats.md # Prompt patterns, codebase prep
├── ExportFormats.md # html / pdf / pptx / canva / url / bundle
└── HandoffBundleSpec.md # Bundle structure for Claude Code handoffExecution Log
{"ts":"ISO8601","workflow":"CreatePrototype","brief":"one-line","outputs":["path1","path2"],"duration_s":42}This log is read-only metadata; it is not part of the public skill distribution.
MIT License
Copyright (c) 2026 Webdesign Skill Contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Webdesign
PAI orchestration skill for Claude Design (claude.ai/design) — Anthropic's visual design product launched April 17, 2026.
What It Does
Drives Claude Design programmatically through the Interceptor skill (real Chrome + authenticated claude.ai session), processes the handoff bundles it produces, and integrates the resulting designs into existing web applications.
Claude Design is the engine. This skill is the cockpit around it.
Why This Exists
Claude Design has no API, no CLI, no plugin. It is a surface on claude.ai. To use it inside a CLI-first workflow — as part of site building, blogging, admin panels, or marketing pages — you need a bridge. Webdesign is that bridge.
Key Capability: Integration-Aware
Most design tools assume greenfield. Webdesign assumes the opposite: you already have an app and need to land a new prototype, page, or component into it cleanly. Workflows like IntegrateIntoApp produce diffs on top of existing code, respecting existing tokens and component patterns.
Prerequisites
- Interceptor skill installed and authenticated to claude.ai
- Active Claude subscription with Claude Design access (Pro / Max / Team / Enterprise)
- For integration: the target project's framework, token file, and component directory
Quick Start
Skill("Webdesign")
# Then ask:
"Create a prototype for a pricing page for an AI security startup"
"Extract the design system from this codebase at ~/projects/my-site"
"Integrate this prototype into the Astro app at ~/projects/landing"The skill routes your request to the right workflow automatically.
Workflows
| Workflow | Purpose |
|---|---|
| CreatePrototype | Brief → polished prototype via Claude Design |
| ExtractDesignSystem | Codebase / brand files → design tokens |
| RefinePrototype | Iterate on existing Claude Design artifact |
| WebsiteToRedesign | Live URL → modernized rebuild |
| ExportToCode | Handoff bundle → local code |
| IntegrateIntoApp | Prototype → diff against existing application |
| DeployDesign | Built design → production host |
Relationship to Other Tools
- `frontend-design` plugin (Anthropic, auto-activates in Claude Code): the downstream code-generation engine when exporting bundles. Not invoked directly by this skill.
- Interceptor skill: required, drives claude.ai/design.
- Art skill: for illustrations, diagrams, header images — not overlapping scope.
- Browser skill: not used; Interceptor is the only supported browser path for authenticated claude.ai work.
License
See LICENSE.txt.
Claude Design Capabilities
Canonical reference for what Claude Design does, its access tiers, and its known limits. Source: the official Anthropic announcement at https://www.anthropic.com/news/claude-design-anthropic-labs (April 17, 2026) and related coverage.
What It Is
Claude Design is an Anthropic Labs research-preview product accessed at claude.ai/design. It is not a CLI tool, plugin, or API. Users interact via natural conversation in a palette UI on claude.ai. Powered by Claude Opus 4.7, Anthropic's most capable vision model.
What It Produces
- Interactive prototypes
- Product wireframes
- Design exploration artifacts
- Pitch decks and slides
- Marketing collateral and one-pagers
- Code-powered prototypes that can incorporate voice, video, shaders, 3D, and built-in AI
- Polished presentations
- Static visuals (not animated — no Lottie/Rive output)
Accepted Inputs
- Text prompts describing the desired design
- Images and sketches (uploaded files)
- Documents: DOCX, PPTX, XLSX
- Codebase links or uploaded code folders
- Website captures (via claude.ai's built-in web tool)
- Existing designs for modification and iteration
- Brand folders containing logos, fonts, style references
Export / Output Formats
| Format | Use case |
|---|---|
| Internal URL | Share within organization, view/edit permissions |
| Folder | Local file export |
| Canva | Collaborative editing, marketing refinement |
| Client deliverables, print | |
| PPTX | Presentation decks |
| Standalone HTML | One-off static pages |
| Claude Code handoff bundle | Production code pipeline — structured for frontend-design plugin |
| ZIP | Bundled asset export |
Key Capabilities
Design System Extraction During Onboarding
"Claude builds a design system for your team by reading your codebase and design files. Every project after that uses your colors, typography, and components automatically."
- Multiple systems per team (e.g., marketing + dashboard)
- Refinable over time via conversational iteration
Live Refinement
- Inline comments on specific elements
- Direct text editing in-place
- Adjustment knobs for spacing, color, layout (live, non-destructive)
- Conversational prompts for structural changes
Organization-Scoped Sharing
- Private by default
- View-only share
- Edit-access share
- Enterprise admin gating
Claude Code Handoff
"Claude packages everything into a handoff bundle that you can pass to Claude Code with a single instruction."
This is the load-bearing integration point between Claude Design (concept/design) and Claude Code (production). The frontend-design plugin (installed via Anthropic's official plugins marketplace) auto-activates when the bundle lands in Claude Code.
Access Tiers
| Tier | Access |
|---|---|
| Free / Starter | No access |
| Pro | Included — standard usage limits (insufficient for sustained pro use) |
| Max | Included — recommended for daily professional use |
| Team | Included |
| Enterprise | OFF by default; admins enable in Organization settings. One-time credit (~20 typical prompts) expiring July 17, 2026 |
Claude Design has its own usage quota, separate from claude.ai chat.
Known Limits (as of launch, April 2026)
- No real-time multiplayer collaboration (unlike Figma). Sharing is async via URL.
- No animation output — Lottie, Rive, WebGL shaders beyond declarative CSS/JS are not first-class outputs.
- No precision print output — professional designers have reported it misses pixel-level constraints for print work.
- Generic aesthetic without a design system — if onboarding is skipped, output drifts toward generic defaults.
- Edge cases require explicit prompting — responsive breakpoints, contrast ratios, dark-mode behavior all need to be called out.
- High token burn — Opus 4.7 powers the generation; heavy use can exhaust Pro-tier limits fast.
Strategic Context
Mike Krieger (Anthropic CPO, ex-Instagram co-founder) led the product. He resigned from Figma's board three days before launch, and Figma stock fell ~7% on announcement day. The product is widely framed as a direct Figma competitor for early-stage design exploration, though Anthropic positions it as complementary rather than replacement.
Relationship to frontend-design Plugin
These are two separate products that form a pipeline:
| Layer | Product | Surface | Role |
|---|---|---|---|
| Concept + design | Claude Design | claude.ai/design | Visual exploration, prototypes, design system |
| Production code | `frontend-design` plugin | Claude Code (auto-activates) | Turns handoff bundles into production-grade code |
The Webdesign skill orchestrates both.
Export Formats
Decision matrix for choosing the right export from Claude Design.
Format Quick Reference
| Format | When to use | Output type | Further processing |
|---|---|---|---|
| Internal URL | Async review, team feedback | Shareable claude.ai URL | None — view-only |
| Canva | Collaborative editing, marketing | Editable Canva project | Canva UI |
| Bundle | Production code pipeline | Directory with PROMPT.md + assets + scaffolding | frontend-design plugin |
| Standalone HTML | One-off landing page, static hosting | Single index.html + assets | Minimal |
| Client deliverable, print | Rendered PDF | None | |
| PPTX | Slide deck presentation | PowerPoint file | Further edit in PPT/Keynote |
| Folder | Local file archive | Directory of assets | Manual processing |
Decision Tree
Q: What's the next step after export?
│
├─ Review / feedback → Internal URL
│
├─ Non-developer will edit → Canva
│
├─ Production code in an existing app → Bundle → IntegrateIntoApp
│
├─ Production code, new standalone app → Bundle → ExportToCode → DeployDesign
│
├─ Static one-off page → Standalone HTML → DeployDesign
│
├─ Client presentation → PDF or PPTX
│
└─ Archive / keep locally → FolderBundle Format (Most Important)
The handoff bundle is the load-bearing output when code is the destination. Structure:
bundle/
├── PROMPT.md # Structured brief for Claude Code
├── tokens.json # Design tokens (colors, typography, spacing)
├── preview.html # Static preview render
├── components/ # Component scaffolds
│ ├── button.tsx
│ ├── card.tsx
│ └── ...
├── assets/ # Images, fonts, icons
│ ├── logo.svg
│ ├── fonts/
│ └── images/
└── README.md # Bundle metadataPROMPT.md — the heart of the bundle
Contains:
- Project purpose + audience
- Aesthetic direction chosen
- Framework target and conventions
- Section-by-section breakdown
- Component inventory needed
- Explicit instructions for the
frontend-designplugin
When you feed the bundle to Claude Code, the plugin reads PROMPT.md first. Everything else is context.
tokens.json — machine-readable design tokens
{
"color": {
"primary": { "50": "#...", "500": "#...", "900": "#..." },
"neutral": { "50": "#...", ...},
"accent": { ...}
},
"typography": {
"display": { "family": "...", "scale": [...] },
"body": { "family": "...", "scale": [...] },
"mono": { "family": "...", "scale": [...] }
},
"spacing": { "unit": 4, "scale": [0,4,8,12,16,24,32,48,64,96] },
"radius": { ... },
"shadow": { ... }
}Framework translators read this directly. Tailwind configs, Styled Components themes, CSS custom properties all derive from this.
HTML Export Caveats
Standalone HTML export is a single file with inlined CSS and (usually) inlined JS. It's great for:
- Static hosts (Cloudflare Pages, Netlify, S3)
- Email-embedded prototypes
- One-off landing pages
It's NOT suitable for:
- Integration into a component-based framework
- Dynamic content / data-fetching
- Multi-page sites (no routing)
For anything beyond a single static page, use Bundle instead.
Canva Export Caveats
Canva exports produce an editable Canva design that preserves:
- Layout structure (as Canva layers)
- Typography (mapped to Canva's font library — may need substitution)
- Color palette (as Canva color swatches)
- Imagery (as Canva-uploaded assets)
Canva is the right export when:
- A non-developer (marketer, founder, designer) needs to refine the design
- Print output is planned (Canva handles CMYK + bleed)
- The design is for social / marketing collateral, not software
Canva is NOT the right export when code is the destination — the round-trip from Canva back to code is lossy.
PDF vs PPTX
| If you need... | Use |
|---|---|
| Single-page client deliverable | |
| Print-ready file | |
| Linear slide deck (intro → content → CTA) | PPTX |
| Presentation that will be further edited in PowerPoint/Keynote | PPTX |
| Fidelity to the Claude Design artifact | PDF (locks appearance) |
| Editable-after-export | PPTX (modifiable slides) |
Token-Only Export
For tight hand-offs where the user will write the code themselves, export just tokens.json:
bun ~/.claude/skills/Webdesign/Tools/DriveClaudeDesign.ts export tokens path/to/out.jsonThis is useful when:
- The development team prefers to write their own components
- You only need to establish the design system, not the implementation
- Integration is token-only (see
IntegrateIntoApp.md→ mode: token-only)
Format Combinations
Sometimes one artifact needs multiple exports:
| Stakeholders | Exports to provide |
|---|---|
| Developer + marketer | Bundle + Canva |
| Developer + designer review | Bundle + Internal URL |
| Client + developer | PDF + Bundle |
| Internal presentation + follow-on build | PPTX + Bundle |
Run multiple export commands in sequence; Claude Design re-renders each format on demand.
Handoff Bundle Specification
Reference for the structure and semantics of a Claude Design → Claude Code handoff bundle.
Bundle Layout
<bundle-root>/
├── PROMPT.md # REQUIRED. Structured brief for the frontend-design plugin.
├── tokens.json # REQUIRED. Design tokens in JSON.
├── preview.html # REQUIRED. Static preview render.
├── README.md # RECOMMENDED. Bundle metadata.
├── manifest.json # RECOMMENDED. Framework + version metadata.
├── components/ # OPTIONAL. Component scaffolds.
│ └── <component>.{tsx,jsx,vue,astro,html}
├── pages/ # OPTIONAL. Page scaffolds (multi-page bundles).
│ └── <route>.{tsx,jsx,vue,astro,html}
├── assets/ # OPTIONAL. Binary assets.
│ ├── images/
│ ├── fonts/
│ ├── icons/
│ └── logos/
└── integration/ # OPTIONAL. Framework-specific config.
├── tailwind.config.ts
├── astro.config.mjs
└── ...File Semantics
PROMPT.md
Frontmatter + structured markdown body. This is the primary contract between Claude Design and the code consumer.
---
generated_by: claude-design
generated_at: 2026-04-18T20:00:00Z
claude_design_session: <uuid>
framework: astro
design_system: <name-or-default>
handoff_type: full | partial | token-only
---
# Project Purpose
One paragraph describing what this interface is for.
# Audience
Who uses this, primary jobs-to-be-done.
# Aesthetic Direction
The chosen aesthetic (brutalist, editorial, retro-futuristic, etc.) with rationale.
# Framework Target
The target framework and any version constraints. Existing project path if integrating.
# Sections
For a page:
- Section 1: purpose + key elements
- Section 2: purpose + key elements
- ...
For a component:
- Purpose
- Props / variants
- States (default / hover / focus / active / disabled)
# Component Inventory
List of components this bundle scaffolds or references.
# Integration Notes
Specific instructions for the code consumer. Token overrides, expected imports, responsive breakpoints, a11y requirements, dark-mode behavior.
# Must-Preserve
Any copy, structure, or elements that MUST land verbatim in the final code.
# Must-NOT
Anti-requirements. Patterns or elements explicitly forbidden.tokens.json
Design tokens in a framework-agnostic JSON schema. Consumers translate to their format.
{
"$schema": "https://claude.ai/design/tokens.schema.json",
"version": "1",
"metadata": {
"name": "<design-system-name>",
"source": "claude-design",
"generated_at": "ISO8601"
},
"color": {
"primary": { "50": "#f0f9ff", "500": "#0ea5e9", "900": "#0c4a6e" },
"neutral": { "0": "#ffffff", "50": "#fafafa", "100": "#f5f5f5", "900": "#111111", "1000": "#000000" },
"accent": { "500": "#f59e0b" },
"semantic": {
"success": "#10b981",
"warning": "#f59e0b",
"error": "#ef4444",
"info": "#3b82f6"
}
},
"typography": {
"display": {
"family": "Fraunces",
"weights": [400, 600, 800],
"scale": { "sm": 24, "md": 32, "lg": 48, "xl": 64, "2xl": 96 }
},
"body": {
"family": "Inter Tight",
"weights": [400, 500, 700],
"scale": { "xs": 12, "sm": 14, "md": 16, "lg": 18, "xl": 20 },
"lineHeight": { "tight": 1.2, "normal": 1.5, "loose": 1.75 }
},
"mono": {
"family": "JetBrains Mono",
"weights": [400, 500],
"scale": { "sm": 12, "md": 14, "lg": 16 }
}
},
"spacing": {
"unit": 4,
"scale": [0, 4, 8, 12, 16, 20, 24, 32, 40, 48, 64, 80, 96, 128]
},
"radius": { "none": 0, "sm": 2, "md": 6, "lg": 12, "xl": 24, "full": 9999 },
"shadow": {
"sm": "0 1px 2px rgba(0,0,0,0.05)",
"md": "0 4px 8px rgba(0,0,0,0.08)",
"lg": "0 12px 24px rgba(0,0,0,0.12)"
},
"motion": {
"duration": { "fast": 150, "normal": 250, "slow": 400 },
"easing": {
"standard": "cubic-bezier(0.4, 0, 0.2, 1)",
"enter": "cubic-bezier(0, 0, 0.2, 1)",
"exit": "cubic-bezier(0.4, 0, 1, 1)"
}
}
}preview.html
A single-file static render that approximates the final design. Useful for:
- Visual diff against generated code
- Fallback when framework translation fails
- Email attachment for stakeholder review
NOT suitable as production code — it is not responsive beyond what Claude Design could inline, and it has no framework integration.
manifest.json
{
"$schema": "https://claude.ai/design/manifest.schema.json",
"version": "1",
"framework": {
"name": "astro",
"version_constraint": ">=4.0.0"
},
"required_packages": {
"tailwindcss": ">=3.4.0",
"@tailwindcss/typography": ">=0.5.0"
},
"components_count": 7,
"pages_count": 1,
"assets_total_bytes": 2485732,
"design_system_ref": "<name-or-inline>",
"claude_design_url": "https://claude.ai/design/<session-id>"
}Framework-Specific Scaffolds
Claude Design emits framework-specific files depending on the framework field:
| Framework | Primary files | Config |
|---|---|---|
| astro | pages/*.astro, components/*.astro, layouts/*.astro | astro.config.mjs, tailwind.config.ts |
| next | app/*/page.tsx, components/*.tsx | next.config.js, tailwind.config.ts |
| react-vite | src/components/*.tsx, src/App.tsx | vite.config.ts, tailwind.config.ts |
| vue | src/components/*.vue, src/App.vue | vite.config.ts, tailwind.config.ts |
| vitepress | .vitepress/theme/components/*.vue, .vitepress/theme/index.ts | .vitepress/config.ts |
| vanilla | index.html, styles.css, script.js | none |
Bundle Validation
Before feeding a bundle to Claude Code, validate structure:
bun ~/.claude/skills/Webdesign/Tools/ProcessHandoffBundle.ts <bundle-dir>The tool checks:
PROMPT.mdexists and has required frontmattertokens.jsonparses and matches schemapreview.htmlexists- Framework-claimed files exist (if manifest.json present)
- Assets referenced in components exist in
assets/ - No secrets or API keys in any text file
Consuming a Bundle
Two paths:
Path A — Full code generation (ExportToCode workflow)
Feed the bundle to Claude Code. The frontend-design plugin auto-activates, reads PROMPT.md, applies tokens.json, and produces production code.
Path B — Integration into existing app (IntegrateIntoApp workflow)
Translate the bundle against the target app's conventions. Produces a diff instead of new files. Reuses existing tokens where possible, flags conflicts explicitly.
Versioning
The bundle schema is versioned. The current version is 1. Future versions will be backward-compatible or gated by the version field in manifest.json and tokens.json.
Input Formats
How to prepare briefs, reference materials, and codebases so Claude Design produces the best output on the first pass.
The Ideal Brief
An effective Claude Design brief has five parts:
1. Purpose — what the interface does, who uses it (1 sentence) 2. Aesthetic direction — ONE committed choice from the catalog (brutally minimal, maximalist chaos, editorial, retro-futuristic, etc.) 3. Constraints — framework, responsive tier, a11y tier, dark-mode, specific elements required 4. Differentiation — the one memorable detail that makes this not-generic 5. Scope — sections, key components, must-haves / must-nots
Template
PURPOSE: A [thing] for [audience] that helps them [core job].
AESTHETIC: [one direction — brutalist / editorial / retro-futuristic / minimal / etc.]
Rationale: [why this fits the audience and job]
CONSTRAINTS:
- Framework: [next / astro / vitepress / react-vite / vanilla]
- Responsive: mobile-first, breakpoints at 640/768/1024/1280
- Accessibility: WCAG 2.1 AA
- Dark mode: [yes / no / both]
- Typography: [pair or "your choice"]
DIFFERENTIATION: [the one memorable element]
SCOPE:
- Section 1: [purpose, key content]
- Section 2: [purpose, key content]
- Must-haves: [list]
- Must-NOTs: [list]Aesthetic Catalog
Use these as starting points, not final prescriptions. Blending two is fine; picking three guarantees muddled output.
| Direction | Feels like | Works for |
|---|---|---|
| Brutally minimal | Raw, unstyled, type-led | Personal sites, essays, manifestos |
| Maximalist chaos | Layered, dense, texture-heavy | Music, fashion, indie games |
| Retro-futuristic | 70s-80s computer-mag energy | Dev tools, infra companies |
| Editorial / magazine | Big photography, grid-breaking | Publications, longform content |
| Brutalist / raw | Concrete textures, stark lines | Indie software, art sites |
| Art deco / geometric | Symmetry, gold, ornamentation | Luxury, finance, legal |
| Soft / pastel | Rounded, warm, approachable | Wellness, health, kids |
| Industrial / utilitarian | Dense info, mono fonts, sharp | Dashboards, admin tools |
| Playful / toy-like | Cartoon, bouncy, vibrant | Consumer apps, games |
| Luxury / refined | Generous space, muted palette, serif | High-end brands, services |
| Editorial newspaper | Serif-led, columnar, dated | Journalism, thinkfluencer |
| Poster / swiss-modernist | Big type, grids, no ornament | Agencies, portfolios |
Avoid Generic Defaults
Claude Design (like any model) defaults to generic when the brief is vague. Explicit directions block:
- ❌ Inter / Roboto / Arial / system-ui-only
- ❌ Purple gradients on white
- ❌ Space Grotesk (overused)
- ❌ Identical card-grid layouts
- ❌ Timid, evenly-distributed color palettes
Request:
- ✅ Distinctive display + body font pair (name both)
- ✅ Dominant color + sharp accents (specify hex or named)
- ✅ Asymmetric or grid-breaking layout
- ✅ One orchestrated animation moment (not scattered micro-interactions)
Reference Images
Feeding 1-5 reference images dramatically lifts first-pass quality. Best practices:
- Mood over copy — reference the vibe, not the content. "This kind of gridded editorial feel" not "copy this site."
- Mix sources — one website screenshot + one poster + one architecture photo produces richer outputs than three website screenshots.
- Label each image — "ref-1-type.png is for typography; ref-2-color.png is for palette; ref-3-layout.png is for composition."
- Avoid AI-generated references — they amplify the generic. Use real human-designed sources.
Codebase Preparation (for design-system extraction)
Before feeding a codebase to ExtractDesignSystem:
1. Curate — don't upload the whole repo. Pull 20-50 focused files. 2. Include — tailwind.config.*, src/styles/, src/components/ui/, primary layout components, package.json. 3. Exclude — node_modules/, dist/, build/, .next/, public/ (unless it has brand assets), test files. 4. Include brand — logos (SVG preferred), custom font files, any brand guide PDF. 5. Flag ambiguity — if the codebase has two incompatible button styles or three competing color palettes, tell Claude Design upfront ("the codebase is mid-migration; prefer the newer *.v2.* files").
Prompt Length Sweet Spot
- Too short (<50 words) — generic output, defaults everywhere
- Sweet spot (100-300 words) — directed output, most detail filled
- Too long (>500 words) — Claude Design starts ignoring parts of the brief
If a brief starts exceeding 300 words, split into phases: first brief for prototype, follow-up refinements for details.
Iteration vs Restart
| Situation | What to do |
|---|---|
| Prototype is 80% right, small fixes needed | RefinePrototype with specific adjustments |
| Aesthetic is wrong | Restart with a sharper aesthetic direction |
| Structure is wrong (missing sections, wrong flow) | Restart with a fuller scope section in the brief |
| "Just not loving it" | Restart — the brief was underspecified; don't refine your way to the right answer |
Examples of Effective Briefs
Brief 1 — Brutalist portfolio
PURPOSE: A single-page portfolio for a print-focused graphic designer.
AESTHETIC: Brutalist. Raw, unstyled, type-led. Think early Ray Gun magazine.
CONSTRAINTS: Framework astro, mobile-first, a11y AA, dark-only, serif display + mono body (no sans).
DIFFERENTIATION: Oversized page number bleeding off the left edge of every section.
SCOPE: Hero with name + date, 8 work samples in irregular asymmetric grid, contact strip at bottom. No nav, no footer, no social icons.
Brief 2 — Minimalist SaaS dashboard
PURPOSE: An admin dashboard for content moderators on a social platform. They review 50-200 items per shift.
AESTHETIC: Industrial minimal. Dense information, mono for data, sans for chrome.
CONSTRAINTS: React+Vite+shadcn, desktop-first (1440+ primary), AA contrast, dark default with light toggle, IBM Plex Sans + Plex Mono.
DIFFERENTIATION: A unified command palette (⌘K) that's visible on first load for 2 seconds, then minimizes — guides users to the keyboard-first workflow.
SCOPE: Item queue (list + detail split view), filters sidebar, action toolbar, keyboard shortcut legend. No marketing content, no avatars, no "welcome back" chrome.
#!/usr/bin/env bun
/*
Usage: DriveClaudeDesign.ts open | prompt "<brief>" | screenshot <out-path>
DriveClaudeDesign.ts export <html|pdf|pptx|canva|url> <out-dir>
DriveClaudeDesign.ts bundle <out-dir>
Prereqs: `interceptor` on PATH and an authenticated claude.ai session.
Examples: DriveClaudeDesign.ts open
DriveClaudeDesign.ts prompt "Create a concise launch deck."
DriveClaudeDesign.ts export pdf ./handoff
Thin Interceptor wrapper. UI targeting uses loud accessibility-tree heuristics.
*/
import { mkdir, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
import { basename, join, resolve } from "node:path";
type TreeNode = { ref?: string; role?: string; name?: string; text?: string; contenteditable?: boolean | string; children?: TreeNode[] };
function resolveInterceptorBin(): string {
const found = Bun.spawnSync(["which", "interceptor"]);
const bin = found.stdout.toString().trim();
if (found.exitCode !== 0 || bin.length === 0) {
console.error("interceptor CLI not found on PATH — install the Interceptor skill (see ~/.claude/skills/Interceptor/SKILL.md)");
process.exit(127);
}
return bin;
}
async function run(argv: string[], timeout = 60_000): Promise<{ code: number; stdout: string; stderr: string }> {
const p = Bun.spawn(argv, { stdout: "pipe", stderr: "pipe", signal: AbortSignal.timeout(timeout) });
const [stdout, stderr, code] = await Promise.all([new Response(p.stdout).text(), new Response(p.stderr).text(), p.exited]);
return { code, stdout, stderr };
}
function walkTree(node: TreeNode, out: TreeNode[] = []): TreeNode[] {
out.push(node);
for (const child of node.children ?? []) walkTree(child, out);
return out;
}
async function getTree(bin: string): Promise<{ raw: string; nodes: TreeNode[] }> {
const result = await run([bin, "tree", "--json"]);
if (result.code !== 0) throw new Error(result.stderr || "interceptor tree failed");
const parsed = JSON.parse(result.stdout) as TreeNode;
return { raw: result.stdout, nodes: walkTree(parsed) };
}
function labelOf(n: TreeNode): string {
return `${n.name ?? ""} ${n.text ?? ""}`.trim();
}
async function dumpMiss(raw: string): Promise<never> {
const out = `/tmp/claude-design-tree-${Date.now()}.json`;
await writeFile(out, raw);
console.error(`Claude Design control heuristic missed; tree dumped to ${out}`);
process.exit(3);
}
async function commandOpen(bin: string): Promise<number> {
const r = await run([bin, "open", "https://claude.ai/design"]);
if (r.stderr) console.error(r.stderr.trim());
return r.code;
}
async function commandPrompt(bin: string, brief?: string): Promise<number> {
if (!brief) {
console.error("usage: DriveClaudeDesign.ts prompt <brief>");
return 2;
}
const tree = await getTree(bin);
// Composer heuristic: Claude Design exposes prompt input as textbox or contenteditable.
// Choose the first such node with an Interceptor ref.
const composer = tree.nodes.find((n) => n.ref && (n.role === "textbox" || n.contenteditable === true || n.contenteditable === "true"));
if (!composer?.ref) await dumpMiss(tree.raw);
const typed = await run([bin, "type", composer.ref, brief]);
if (typed.code !== 0) return typed.code;
const buttons = tree.nodes.filter((n) => n.ref && n.role === "button");
// Send heuristic: prefer explicit Send text; icon-only UIs may expose submit-like
// text, and a single-button composer is the last resort.
const send = buttons.find((n) => /send|submit|arrow/i.test(labelOf(n))) ?? (buttons.length === 1 ? buttons[0] : undefined);
if (!send?.ref) await dumpMiss(tree.raw);
const clicked = await run([bin, "click", send.ref]);
return clicked.code;
}
async function commandScreenshot(bin: string, outPath?: string): Promise<number> {
if (!outPath) {
console.error("usage: DriveClaudeDesign.ts screenshot <out-path>");
return 2;
}
const target = resolve(outPath);
await mkdir(resolve(target, ".."), { recursive: true }).catch(() => undefined);
const r = await run([bin, "screenshot", target], 30_000);
if (r.stdout) console.log(r.stdout.trim());
if (r.stderr) console.error(r.stderr.trim());
return r.code;
}
async function newestDownload(seconds: number, ext?: RegExp): Promise<string | null> {
const dir = join(Bun.env.HOME ?? "", "Downloads");
const entries = await readdir(dir, { withFileTypes: true }).catch(() => []);
const min = Date.now() - seconds * 1000;
let best: { path: string; mtime: number } | null = null;
for (const entry of entries) {
if (!entry.isFile() || entry.name.startsWith(".")) continue;
if (ext && !ext.test(entry.name)) continue;
const p = join(dir, entry.name);
const s = await stat(p).catch(() => null);
if (!s || s.mtimeMs < min) continue;
if (!best || s.mtimeMs > best.mtime) best = { path: p, mtime: s.mtimeMs };
}
return best?.path ?? null;
}
async function commandExport(bin: string, format?: string, outDir?: string): Promise<number> {
const allowed = ["html", "pdf", "pptx", "canva", "url"];
if (!format || !outDir || !allowed.includes(format)) {
console.error("usage: DriveClaudeDesign.ts export <html|pdf|pptx|canva|url> <out-dir>");
return 2;
}
const tree = await getTree(bin);
// Export heuristic: target Export by accessible text, then a menu item/button
// containing the requested format text after the menu opens.
const exportButton = tree.nodes.find((n) => n.ref && n.role === "button" && /export/i.test(labelOf(n)));
if (!exportButton?.ref) await dumpMiss(tree.raw);
let r = await run([bin, "click", exportButton.ref]);
if (r.code !== 0) return r.code;
await Bun.sleep(500);
const menu = await getTree(bin);
const item = menu.nodes.find((n) => n.ref && /menuitem|button|link/i.test(n.role ?? "") && labelOf(n).toLowerCase().includes(format.toLowerCase()));
if (!item?.ref) await dumpMiss(menu.raw);
r = await run([bin, "click", item.ref]);
if (r.code !== 0) return r.code;
await Bun.sleep(3000);
const downloaded = await newestDownload(10);
if (!downloaded) {
console.error("No recent download found after export.");
return 4;
}
const dir = resolve(outDir);
await mkdir(dir, { recursive: true });
const target = join(dir, basename(downloaded));
await rename(downloaded, target);
console.log(target);
return 0;
}
async function commandBundle(bin: string, outDir?: string): Promise<number> {
if (!outDir) {
console.error("usage: DriveClaudeDesign.ts bundle <out-dir>");
return 2;
}
const tree = await getTree(bin);
// Handoff heuristic: Claude Design has varied handoff copy, so match several
// public-facing labels and require a clickable ref.
const handoff = tree.nodes.find((n) => n.ref && /Claude Code|handoff|Send to Claude/i.test(labelOf(n)));
if (!handoff?.ref) await dumpMiss(tree.raw);
const clicked = await run([bin, "click", handoff.ref]);
if (clicked.code !== 0) return clicked.code;
await Bun.sleep(3000);
const zip = await newestDownload(20, /\.zip$/i);
if (!zip) {
console.error("No recent handoff ZIP found.");
return 4;
}
const dir = resolve(outDir);
await mkdir(dir, { recursive: true });
const unzip = await run(["unzip", "-q", zip, "-d", dir]);
if (unzip.code !== 0) {
console.error(unzip.stderr || "unzip failed");
return 5;
}
await rm(zip).catch(() => undefined);
console.log(dir);
return 0;
}
async function main(): Promise<void> {
const [verb, ...args] = Bun.argv.slice(2);
if (!verb) {
console.error("usage: DriveClaudeDesign.ts <open|prompt|screenshot|export|bundle> ...");
return;
}
const bin = resolveInterceptorBin();
let code = 2;
if (verb === "open") code = await commandOpen(bin);
else if (verb === "prompt") code = await commandPrompt(bin, args.join(" "));
else if (verb === "screenshot") code = await commandScreenshot(bin, args[0]);
else if (verb === "export") code = await commandExport(bin, args[0], args[1]);
else if (verb === "bundle") code = await commandBundle(bin, args[0]);
else console.error("usage: DriveClaudeDesign.ts <open|prompt|screenshot|export|bundle> ...");
process.exit(code);
}
await main();
#!/usr/bin/env bun
import { readdir, readFile, stat } from "node:fs/promises";
import { basename, extname, join, relative, resolve, sep } from "node:path";
type Frontmatter = Record<string, string>;
type Assets = {
images: string[];
fonts: string[];
logos: string[];
components: string[];
code: string[];
tokens: unknown | null;
tokensError?: string;
notes: string[];
other: string[];
};
type Output = {
bundleDir: string;
promptFrontmatter: Frontmatter;
promptBody: string;
assets: Assets;
summary: { totalFiles: number; categories: Record<string, number> };
};
const imageExt = new Set([".png", ".jpg", ".jpeg", ".webp", ".gif", ".svg", ".avif"]);
const fontExt = new Set([".woff", ".woff2", ".ttf", ".otf", ".eot"]);
const codeExt = new Set([".ts", ".js", ".mjs", ".cjs", ".css", ".scss", ".html"]);
const componentExt = new Set([".tsx", ".jsx", ".vue", ".svelte"]);
const noteNames = new Set(["README.md", "HANDOFF.md", "NOTES.md"]);
function parsePrompt(text: string): { frontmatter: Frontmatter; body: string } {
if (!text.startsWith("---\n")) return { frontmatter: {}, body: text.trim() };
const end = text.indexOf("\n---", 4);
if (end < 0) return { frontmatter: {}, body: text.trim() };
const frontmatter: Frontmatter = {};
for (const line of text.slice(4, end).split(/\r?\n/)) {
const m = /^([A-Za-z0-9_-]+):\s*(.*)$/.exec(line);
if (!m) continue;
frontmatter[m[1]] = m[2].replace(/^["']|["']$/g, "");
}
return { frontmatter, body: text.slice(end + 5).trim() };
}
async function walk(dir: string, root: string, depth = 0, acc: string[] = []): Promise<string[]> {
if (depth > 6) return acc;
if (acc.length > 5000) throw new Error("file-count-cap-exceeded");
for (const entry of await readdir(dir, { withFileTypes: true })) {
if (entry.name.startsWith(".")) continue;
const abs = join(dir, entry.name);
if (entry.isDirectory()) await walk(abs, root, depth + 1, acc);
else if (entry.isFile()) {
acc.push(relative(root, abs));
if (acc.length > 5000) throw new Error("file-count-cap-exceeded");
}
}
return acc;
}
function isUnder(rel: string, part: string): boolean {
return rel.split(sep).includes(part);
}
function emptyAssets(): Assets {
return { images: [], fonts: [], logos: [], components: [], code: [], tokens: null, notes: [], other: [] };
}
async function parseBundle(dir: string): Promise<Output> {
const bundleDir = resolve(dir);
const s = await stat(bundleDir).catch(() => null);
if (!s?.isDirectory()) {
console.log(JSON.stringify({ error: "no-such-bundle-dir", bundleDir }));
process.exit(2);
}
const promptPath = join(bundleDir, "PROMPT.md");
if (!(await Bun.file(promptPath).exists())) {
console.log(JSON.stringify({ error: "no-prompt-md", bundleDir }));
process.exit(2);
}
const prompt = parsePrompt(await Bun.file(promptPath).text());
const files = await walk(bundleDir, bundleDir);
const assets = emptyAssets();
let tokenPath: string | null = null;
for (const rel of files) {
const name = basename(rel);
const ext = extname(rel).toLowerCase();
let caught = false;
if (imageExt.has(ext)) {
assets.images.push(rel);
caught = true;
if (/logo|mark|brand/i.test(name)) assets.logos.push(rel);
}
if (fontExt.has(ext)) {
assets.fonts.push(rel);
caught = true;
}
if (name === "tokens.json") {
tokenPath = join(bundleDir, rel);
caught = true;
}
if (isUnder(rel, "components") || componentExt.has(ext)) {
assets.components.push(rel);
caught = true;
} else if (codeExt.has(ext)) {
assets.code.push(rel);
caught = true;
}
if (noteNames.has(name)) {
assets.notes.push(rel);
caught = true;
}
if (!caught && name !== "PROMPT.md") assets.other.push(rel);
}
if (tokenPath) {
try {
assets.tokens = JSON.parse(await readFile(tokenPath, "utf8")) as unknown;
} catch (e) {
assets.tokensError = e instanceof Error ? e.message : String(e);
}
}
const categories: Record<string, number> = {};
for (const key of ["images", "fonts", "logos", "components", "code", "notes", "other"]) {
categories[key] = assets[key as keyof Pick<Assets, "images" | "fonts" | "logos" | "components" | "code" | "notes" | "other">].length;
}
categories.tokens = assets.tokens || assets.tokensError ? 1 : 0;
return { bundleDir, promptFrontmatter: prompt.frontmatter, promptBody: prompt.body, assets, summary: { totalFiles: files.length, categories } };
}
async function renderBrief(out: Output): Promise<string> {
const lines: string[] = [`# Handoff Bundle Brief: ${basename(out.bundleDir)}`, "", "## Prompt frontmatter"];
const entries = Object.entries(out.promptFrontmatter);
lines.push(...(entries.length ? entries.map(([k, v]) => `- ${k}: ${v}`) : ["- none"]));
lines.push("", "## Contents");
for (const key of ["images", "fonts", "logos", "components", "code", "notes", "other"] as const) {
const list = out.assets[key];
lines.push(`- ${list.length} ${key}${list.length ? ` (${list.slice(0, 10).join(", ")})` : ""}`);
}
lines.push(`- ${out.summary.categories.tokens} tokens`);
lines.push("", "## Integration notes");
const notes: string[] = [];
for (const rel of out.assets.notes) notes.push(await readFile(join(out.bundleDir, rel), "utf8"));
lines.push(notes.join("\n\n").trim() || "No notes files found.");
lines.push("", "## Suggested next step", "Pass this bundle to the frontend build context with the following one-line instruction:");
lines.push(`> Integrate the assets in \`${out.bundleDir}\` using \`tokens.json\` (if present) and components/ directory as the design reference.`);
return lines.join("\n");
}
async function main(): Promise<void> {
const [dir, flag] = Bun.argv.slice(2);
if (!dir) {
console.error("usage: ProcessHandoffBundle.ts <bundle-dir> [--brief]");
return;
}
if (flag && flag !== "--brief") {
console.error("usage: ProcessHandoffBundle.ts <bundle-dir> [--brief]");
process.exit(2);
}
const out = await parseBundle(dir);
console.log(flag === "--brief" ? await renderBrief(out) : JSON.stringify(out, null, 2));
}
await main();
#!/usr/bin/env bun
/*
Usage:
VerifyDesign.ts <url-or-path> <out-dir> [--viewport WIDTHxHEIGHT] [--a11y|--no-a11y]
Runs a thin Interceptor-driven smoke check for a rendered design. The viewport is
validated and reported, but not applied because Interceptor exposes no viewport
verb. Accessibility checks are viewport-independent tree heuristics, not axe-core.
*/
import { stat } from "node:fs/promises";
import { join, resolve } from "node:path";
import { pathToFileURL } from "node:url";
type TreeNode = {
ref?: string;
role?: string;
name?: string;
text?: string;
alt?: string;
href?: string;
level?: number;
children?: TreeNode[];
};
type Violation = { type: string; count: number; examples: { ref?: string; text?: string }[] };
type A11yResult = {
engine: "interceptor-tree-heuristic";
limitations: string[];
violations: Violation[];
pass: boolean;
};
function resolveInterceptorBin(): string {
const found = Bun.spawnSync(["which", "interceptor"]);
const bin = found.stdout.toString().trim();
if (found.exitCode !== 0 || bin.length === 0) {
console.error("interceptor CLI not found on PATH — install the Interceptor skill (see ~/.claude/skills/Interceptor/SKILL.md)");
process.exit(127);
}
return bin;
}
async function run(argv: string[], timeout: number): Promise<{ code: number; stdout: string; stderr: string }> {
const p = Bun.spawn(argv, { stdout: "pipe", stderr: "pipe", signal: AbortSignal.timeout(timeout) });
const [stdout, stderr, code] = await Promise.all([new Response(p.stdout).text(), new Response(p.stderr).text(), p.exited]);
return { code, stdout, stderr };
}
function walkTree(node: TreeNode, out: TreeNode[] = []): TreeNode[] {
out.push(node);
for (const child of node.children ?? []) walkTree(child, out);
return out;
}
function textOf(n: TreeNode): string {
return `${n.name ?? ""} ${n.text ?? ""}`.trim();
}
function add(map: Map<string, Violation>, type: string, node: TreeNode): void {
const v = map.get(type) ?? { type, count: 0, examples: [] };
v.count += 1;
if (v.examples.length < 5) v.examples.push({ ref: node.ref, text: textOf(node) });
map.set(type, v);
}
function a11yFromTree(root: TreeNode): A11yResult {
const nodes = walkTree(root);
const violations = new Map<string, Violation>();
let previousHeading = 0;
let sawHeading = false;
for (const n of nodes) {
const role = (n.role ?? "").toLowerCase();
const label = textOf(n);
if (role === "img" && !label && !n.alt) add(violations, "img-alt", n);
if (role === "button" && !label) add(violations, "button-name", n);
if (role === "a" && (!label || !n.href)) add(violations, "link-name", n);
if (["textbox", "combobox", "spinbutton"].includes(role) && !label) add(violations, "form-label", n);
if (role === "heading" && typeof n.level === "number") {
if (!sawHeading && n.level > 1) add(violations, "heading-order", n);
if (sawHeading && n.level > previousHeading + 1) add(violations, "heading-order", n);
sawHeading = true;
previousHeading = n.level;
}
}
const list = [...violations.values()];
return {
engine: "interceptor-tree-heuristic",
limitations: ["no-contrast-check", "no-dynamic-aria-live-check", "no-css-parsed-check"],
violations: list,
pass: list.length === 0,
};
}
function parseArgs(): { input: string; outDir: string; w: number; h: number; a11y: boolean } {
const args = Bun.argv.slice(2);
const input = args.shift();
const outDir = args.shift();
if (!input || !outDir) {
console.error("usage: VerifyDesign.ts <url-or-path> <out-dir> [--viewport WIDTHxHEIGHT] [--a11y|--no-a11y]");
process.exit(2);
}
let viewport = "1440x900";
let a11y = true;
while (args.length) {
const flag = args.shift();
if (flag === "--viewport") viewport = args.shift() ?? "";
else if (flag === "--a11y") a11y = true;
else if (flag === "--no-a11y") a11y = false;
else {
console.error(`unknown flag: ${flag ?? ""}`);
process.exit(2);
}
}
const m = /^(\d+)x(\d+)$/.exec(viewport);
const w = m ? Number(m[1]) : 0;
const h = m ? Number(m[2]) : 0;
if (!m || w < 320 || h < 320 || w > 7680 || h > 7680) {
console.error("invalid viewport; expected WIDTHxHEIGHT with each value in [320, 7680]");
process.exit(2);
}
return { input, outDir, w, h, a11y };
}
async function resolveUrl(input: string): Promise<{ url: string; resolvedUrl: string }> {
if (/^https?:\/\//.test(input)) return { url: input, resolvedUrl: input };
const abs = resolve(input);
const s = await stat(abs).catch(() => null);
if (!s) {
console.error(`path does not exist: ${abs}`);
process.exit(2);
}
return { url: input, resolvedUrl: pathToFileURL(abs).href };
}
async function main(): Promise<void> {
if (Bun.argv.slice(2).length === 0) {
console.error("usage: VerifyDesign.ts <url-or-path> <out-dir> [--viewport WIDTHxHEIGHT] [--a11y|--no-a11y]");
return;
}
const opts = parseArgs();
const { url, resolvedUrl } = await resolveUrl(opts.input);
const outDir = resolve(opts.outDir);
const made = await run(["mkdir", "-p", outDir], 5_000);
if (made.code !== 0) {
console.error(made.stderr || "failed to create output directory");
process.exit(2);
}
const bin = resolveInterceptorBin();
const timestamp = new Date().toISOString();
await run([bin, "open", resolvedUrl], 60_000);
await run([bin, "wait-stable"], 30_000);
const shot = join(outDir, `${timestamp.replace(/[:.]/g, "-")}.png`);
let screenshot: string | null = shot;
let screenshotError: string | undefined;
const s = await run([bin, "screenshot", shot], 30_000);
if (s.code !== 0) {
screenshot = null;
screenshotError = s.stderr || "screenshot failed";
}
let a11y: A11yResult | { skipped: true };
if (opts.a11y) {
const tree = await run([bin, "tree", "--json"], 30_000);
if (tree.code === 0) {
a11y = a11yFromTree(JSON.parse(tree.stdout) as TreeNode);
} else {
a11y = {
engine: "interceptor-tree-heuristic",
limitations: ["no-contrast-check", "no-dynamic-aria-live-check", "no-css-parsed-check"],
violations: [{ type: "tree-unavailable", count: 1, examples: [{ text: tree.stderr || "tree failed" }] }],
pass: false,
};
}
} else {
a11y = { skipped: true };
}
const a11yPass = "skipped" in a11y ? true : a11y.pass;
const pass = screenshot !== null && a11yPass;
const result = {
url,
resolvedUrl,
viewport: { w: opts.w, h: opts.h },
screenshot,
...(screenshotError ? { screenshotError } : {}),
a11y,
pass,
timestamp,
};
console.log(JSON.stringify(result, null, 2));
process.exit(pass ? 0 : 1);
}
await main();
CreatePrototype
Brief → polished prototype via Claude Design.
Trigger Phrases
"design a prototype", "create a prototype", "mockup", "build a design", "make a landing page design", "design a dashboard"
Inputs
Required:
- Brief — one-to-three sentences describing what to build (purpose, audience, mood)
Optional (strongly recommended):
- Reference images — 1-5 local image paths for visual inspiration
- Brand assets — logo path, font files, existing color palette
- Framework target — "astro", "next", "vitepress", "vanilla-html" — informs the downstream handoff
- Existing project path — if this prototype will land inside an existing app (triggers
IntegrateIntoAppas a follow-on) - Aesthetic direction — one of: minimal, maximalist, retro-futuristic, editorial, brutalist, art-deco, luxury, playful, industrial. If omitted, Claude Design picks.
Workflow
1. Preflight
Confirm all prerequisites from SKILL.md → Prerequisites. If any fail, halt with remediation.
# Verify Interceptor available
interceptor --version || echo "ABORT: Interceptor skill not installed"2. Construct the Brief
Compose a single prompt for Claude Design. Include:
- One-sentence purpose — what the page/component does, who uses it
- Aesthetic direction — explicit (do not let Claude Design default to generic)
- Constraints — responsive breakpoints, dark mode, accessibility tier, framework
- Differentiation hook — the one memorable detail
- Scope — number of sections, key components, must-have elements
Use the prompt patterns in References/InputFormats.md.
3. Open Claude Design
bun ~/.claude/skills/Webdesign/Tools/DriveClaudeDesign.ts openThis opens claude.ai/design in the authenticated Interceptor-controlled Chrome session. First-run may require a headed login; subsequent runs are headless.
4. Submit the Brief
bun ~/.claude/skills/Webdesign/Tools/DriveClaudeDesign.ts prompt "$(cat /tmp/brief.md)"Wait for Claude Design to produce the first version (typically 20-60 seconds on Opus 4.7).
5. Capture the Output
OUT=~/Downloads/webdesign/$(date +%Y%m%d-%H%M%S)
mkdir -p "$OUT"
bun ~/.claude/skills/Webdesign/Tools/DriveClaudeDesign.ts screenshot "$OUT/v1.png"Review the screenshot. If it matches the brief, proceed to step 6. If not, hand to RefinePrototype.md.
6. Export
Choose based on next step:
| Next step | Export format |
|---|---|
| Review / feedback | url (shareable internal URL) |
| Collaborative editing | canva |
| Local code integration | bundle (handoff to Claude Code) |
| Slide deck / client presentation | pptx or pdf |
| Static one-off page | html |
bun ~/.claude/skills/Webdesign/Tools/DriveClaudeDesign.ts export bundle "$OUT"7. Verify
bun ~/.claude/skills/Webdesign/Tools/VerifyDesign.ts "$OUT/index.html" "$OUT/verify"Produces a screenshot at the expected viewport plus an axe-core accessibility report. Fix any critical issues before declaring done.
8. Handoff
If this prototype feeds into larger site work:
Skill("Webdesign") → Workflows/IntegrateIntoApp.mdPass the bundle path + target project path.
Output
- Screenshot(s) in
$OUT/ - Export artifact (bundle / html / canva link / pptx)
- Accessibility report
- One-line entry in
~/.claude/
Common Pitfalls
- Vague brief — "make it look nice" yields generic. Be explicit about aesthetic direction, mood, and differentiation.
- Skipping reference images — Claude Design with visual references produces dramatically better first drafts than text-only briefs.
- Not specifying framework — the handoff bundle scaffolds differently for React vs Vue vs vanilla; pick before export.
- Exporting to HTML when you wanted bundle — HTML export is static and does not carry tokens / components. For any code-integration workflow, always export
bundle.
Time Estimate
3-8 minutes for a single-iteration prototype. Add 1-3 minutes per refinement round.
DeployDesign
Built design → production host.
Trigger Phrases
"deploy the design", "ship to production", "publish this prototype", "put this online"
Inputs
Required:
- Source — path to the generated code (from
ExportToCode) or integrated branch (fromIntegrateIntoApp) - Target host — one of: cloudflare-pages, vercel, netlify, github-pages, static-s3, custom
Optional:
- Domain — custom domain if configured
- Preview flag — deploy to a preview URL instead of production
Workflow
1. Preflight
Confirm:
- Source directory builds clean (
bun install && bun run build) - No secrets in the build output (
rg -i "API_KEY|SECRET|PRIVATE_KEY|sk_live|sk_test" "$OUT/dist") - Target host CLI is installed (
wrangler,vercel,netlify,gh)
2. Build
cd "$SOURCE"
bun install --frozen-lockfile
bun run buildVerify dist/ (or .vercel/output/, .netlify/) is populated.
3. Deploy — host-specific
Cloudflare Pages:
bunx wrangler pages deploy dist --project-name "$PROJECT" ${PREVIEW:+--branch preview}Vercel:
vercel deploy ${PREVIEW:+--prebuilt} ${PRODUCTION:+--prod}Netlify:
netlify deploy --dir dist ${PRODUCTION:+--prod}GitHub Pages:
gh workflow run pages.yml # assumes a configured workflowStatic S3 / custom:
aws s3 sync dist "s3://$BUCKET" --delete4. Verify Live
Post-deploy, hit the URL and screenshot:
bun ~/.claude/skills/Webdesign/Tools/VerifyDesign.ts "https://$DEPLOYED_URL" "$OUT/live-verify"Compare against $OUT/preview.png — the live site should match within visual tolerance.
5. Accessibility + Performance Probe
bun ~/.claude/skills/Webdesign/Tools/VerifyDesign.ts --a11y --lighthouse "https://$DEPLOYED_URL" "$OUT/live-quality"Output includes:
- axe-core a11y scan results
- Lighthouse scores (performance, accessibility, best-practices, SEO)
6. Report
Return:
- Deployed URL
- Screenshot proof
- a11y + lighthouse summary (pass / fail / warn)
- Rollback command (host-specific)
Preview vs Production
Always deploy to preview first for non-trivial changes:
# Preview
Skill("Webdesign") → Workflows/DeployDesign.md --preview
# Review preview URL
# Only then deploy to production
Skill("Webdesign") → Workflows/DeployDesign.md --productionCommon Pitfalls
- Deploying without building — some frameworks need an explicit build step.
bun run buildis not optional. - Skipping the secret scan — leaked API keys in a bundle are the most common preventable incident. The grep is 200ms of paranoia that prevents rotation hell.
- Skipping live verify — a successful deploy command means "files uploaded," not "site works." Always hit the URL post-deploy.
- Deploying to production on first ship — preview first. Every time.
- Forgetting to configure the domain — check DNS / custom-domain settings BEFORE deploying if a specific domain is expected.
Rollback
Each host has a rollback mechanism:
- Cloudflare Pages:
wrangler pages deployment list→ pick previous → promote - Vercel:
vercel ls→vercel promote <old-deployment> - Netlify:
netlify rollback - GitHub Pages: revert the commit and re-run the workflow
- S3: the host has a previous
dist/snapshot; re-sync from that
Time Estimate
2-5 minutes for a clean deploy + verify. Add 5-15 minutes per rollback or failed-verify iteration.
ExportToCode
Claude Design handoff bundle → production code via the frontend-design plugin.
Trigger Phrases
"export to code", "ship to code", "send to Claude Code", "process handoff bundle", "turn this into a component"
Inputs
Required — one of:
- Active Claude Design session with a prototype ready to export
- Existing handoff bundle — a directory previously exported from Claude Design
Optional:
- Framework target — overrides the bundle's default framework
- Output directory — where the generated code should land
Workflow
1. Export from Claude Design (if not already done)
OUT=~/Downloads/webdesign/export/$(date +%Y%m%d-%H%M%S)
mkdir -p "$OUT"
bun ~/.claude/skills/Webdesign/Tools/DriveClaudeDesign.ts export bundle "$OUT/bundle"The bundle format produces a directory containing:
PROMPT.md— a structured handoff brief written by Claude Designtokens.json— design tokens (colors, typography, spacing)components/— component scaffolds (if applicable)assets/— images, fonts, iconspreview.html— static reference render
2. Parse the Bundle
bun ~/.claude/skills/Webdesign/Tools/ProcessHandoffBundle.ts "$OUT/bundle" > "$OUT/bundle.json"
bun ~/.claude/skills/Webdesign/Tools/ProcessHandoffBundle.ts "$OUT/bundle" --brief > "$OUT/integration-brief.md"The --brief flag emits a markdown summary ready to feed into the next agent (the frontend-design plugin).
3. Hand Off to the frontend-design Plugin
The Anthropic frontend-design plugin auto-activates in Claude Code whenever a frontend build request arrives. Feed it the bundle + brief:
"Build the frontend from this handoff bundle: $OUT/bundle. Follow the integration brief at $OUT/integration-brief.md. Target framework: $FRAMEWORK. Place output in $OUT/code/."
The plugin does the actual code generation — bold aesthetic, distinctive typography, cohesive palette, production-grade — using the tokens and prompt the bundle carries.
4. Verify the Generated Code
# Start a local preview (depends on framework)
cd "$OUT/code"
bun install
bun dev &
DEV_PID=$!
sleep 3
# Screenshot the running app
bun ~/.claude/skills/Webdesign/Tools/VerifyDesign.ts http://localhost:5173 "$OUT/verify"
kill $DEV_PIDCompare $OUT/verify/screenshot.png against $OUT/bundle/preview.html — fidelity should be within visual tolerance. Flag any regressions.
5. Accessibility Check
bun ~/.claude/skills/Webdesign/Tools/VerifyDesign.ts --a11y http://localhost:5173 "$OUT/a11y"Any critical or serious a11y violations block shipping. Fix in code before proceeding.
6. Handoff to Next Step
- Integrating into an existing app →
Workflows/IntegrateIntoApp.mdwith$OUT/codeas source - Deploying standalone →
Workflows/DeployDesign.mdwith$OUT/codeas source
Framework-Specific Notes
| Framework | Bundle produces | Typical adjustments |
|---|---|---|
| React + Vite | src/ with components, tailwind.config.ts, package.json | Add routing, state mgmt if needed |
| Next.js | app/ with pages, layouts, server components | Wire data-fetching, auth |
| Astro | src/pages/, src/components/ with Astro + React islands | Set integrations in astro.config.mjs |
| VitePress | .vitepress/theme/ overrides + custom layout components | Limited — static content only |
| Vue | src/components/ Vue 3 composition API | Add Pinia/router if needed |
| Vanilla HTML | single index.html + styles.css + script.js | Easiest to drop into static hosts |
Common Pitfalls
- Skipping `ProcessHandoffBundle` — reading the raw bundle into the frontend-design plugin works but loses the structured brief. Always generate the brief first.
- Framework mismatch — if the bundle was exported for React and you feed it to an Astro project, results drift. Re-export with the right framework or use
IntegrateIntoAppfor translation. - Trusting preview.html as production —
preview.htmlis a static one-off render. It is NOT production code. Always run the actual framework build. - No verification — exported code that "should work" often has subtle issues (missing deps, broken imports, a11y regressions). Verify before handing downstream.
Time Estimate
2-5 minutes for bundle parse + plugin handoff. Add 2-10 minutes for verification and a11y pass.
ExtractDesignSystem
Codebase / brand assets → design tokens Claude Design uses on every subsequent generation.
Trigger Phrases
"extract design system", "pull tokens from this repo", "learn this brand", "teach Claude Design our colors", "onboard design system"
Why This Runs First
Claude Design's onboarding step reads your codebase and existing design files to build a team design system. Every prototype it generates afterward uses YOUR colors, typography, and components automatically. Run this BEFORE CreatePrototype for any branded work — otherwise Claude Design uses generic defaults and the prototypes drift from your brand.
Inputs
Required — one or more of:
- Codebase path — a local project directory with CSS/Tailwind/component files
- Design files — Figma export, PDF brand guide, PPTX style guide
- Brand folder — logos, fonts, color swatches, style references
Optional:
- System name — if you maintain multiple design systems (e.g., "marketing" vs "dashboard")
- Scope hints — tell Claude Design what to extract first ("focus on color palette and typography, ignore component code")
Workflow
1. Preflight
interceptor --version || echo "ABORT: Interceptor not installed"
ls "$INPUT_PATH" || echo "ABORT: input path not readable"2. Prepare Input Package
Depending on input type:
Codebase:
# Collect design-relevant files into a tempdir for upload
TMP=$(mktemp -d)
cp -r "$REPO/tailwind.config.*" "$TMP/" 2>/dev/null
cp -r "$REPO/src/styles" "$TMP/" 2>/dev/null
cp -r "$REPO/src/components/ui" "$TMP/" 2>/dev/null
cp "$REPO/package.json" "$TMP/" 2>/dev/nullBrand folder:
# Just point Claude Design at the folder — it reads images, fonts, and docs directly
TMP="$BRAND_FOLDER"3. Open Claude Design Onboarding
bun ~/.claude/skills/Webdesign/Tools/DriveClaudeDesign.ts openThen navigate to the design system section. The skill sends a prompt like:
"Build a design system from the files I'm about to upload. Extract colors, typography, spacing, and component patterns. Name the system '$NAME'. Flag any conflicts or ambiguities."
4. Upload Files
bun ~/.claude/skills/Webdesign/Tools/DriveClaudeDesign.ts upload "$TMP"5. Wait for System Extraction
Claude Design takes 30s-3min depending on codebase size. Output appears in the design system panel with:
- Color palette (primary, neutrals, accents)
- Typography scale (display, body, mono)
- Spacing grid
- Component inventory (buttons, cards, navs, inputs)
- Flagged conflicts / ambiguities
6. Review and Refine
Screenshot the extracted system:
bun ~/.claude/skills/Webdesign/Tools/DriveClaudeDesign.ts screenshot "$OUT/extracted-system.png"If Claude Design flagged conflicts (e.g., "three different button styles in codebase — pick canonical"), resolve in the conversational UI. The adjustment-knob UX lets you tweak tokens live.
7. Save System
Claude Design persists the system server-side on your account. It will be auto-applied to every subsequent prototype generated in this workspace. You can also export the tokens as JSON:
bun ~/.claude/skills/Webdesign/Tools/DriveClaudeDesign.ts export tokens "$OUT/tokens.json"Output
$OUT/extracted-system.png— visual snapshot of the extracted system$OUT/tokens.json— machine-readable token export (colors, typography, spacing)$OUT/system-notes.md— flagged conflicts and resolutions- Claude Design workspace primed for
CreatePrototype
Common Pitfalls
- Feeding a full repo — Claude Design gets overwhelmed by 10K+ files. Curate a focused bundle (~20-50 files max).
- Uploading generated output — If your codebase has
dist/orbuild/, exclude them. Upload source only. - Skipping conflict resolution — unresolved conflicts lead to inconsistent prototypes downstream. Resolve at extraction time, not generation time.
- One-shot expecting perfection — extraction is iterative. Plan for 1-2 refinement rounds before the system is solid.
Time Estimate
5-15 minutes depending on codebase complexity and conflict count.
IntegrateIntoApp
Land a Claude Design prototype INTO an existing application as a framework-aware diff, not a greenfield scaffold.
Why This Workflow Exists
Most design tools assume you're starting fresh. Real site work isn't like that. You already have an Astro app with a theme, a Next.js dashboard with a component library, a VitePress blog with a VP config. When a prototype lands, it must:
- Reuse the app's existing design tokens (no second color palette)
- Match the app's component patterns (don't introduce a new button style)
- Respect the app's router and layout conventions
- Merge, not replace, unless explicitly asked to replace
This workflow does that.
Trigger Phrases
"integrate this into", "patch into the app", "land this in the codebase", "merge this prototype", "add this page to the site"
Inputs
Required:
- Prototype source — either an active Claude Design session, a handoff bundle path, or a generated code directory (from
ExportToCode) - Target project path — local path to the existing app
- Integration target — a route/page/component identifier inside the app ("the pricing page", "the sidebar nav", "a new blog layout")
Optional:
- Preserve list — existing code/tokens/components that must NOT be overwritten
- Replace flag — explicit permission to replace existing components in scope
Workflow
1. Audit the Target Project
Before any code lands, understand what's already there.
TARGET="$PROJECT_PATH"
OUT=~/Downloads/webdesign/integrate/$(date +%Y%m%d-%H%M%S)
mkdir -p "$OUT"
# Detect framework
cat "$TARGET/package.json" | jq -r '.dependencies, .devDependencies | keys[]' | grep -iE "^(next|astro|vitepress|vite|vue|remix|nuxt|sveltekit)$" | head -1 > "$OUT/framework.txt"
# Capture existing design tokens
find "$TARGET" -maxdepth 4 \( -name "tailwind.config.*" -o -name "tokens.*" -o -name "theme.*" -o -name "variables.css" \) | head -20 > "$OUT/token-files.txt"
# Capture existing component patterns
find "$TARGET/src" -type d -name "components" -o -name "ui" 2>/dev/null > "$OUT/component-dirs.txt"2. Extract the App's Design System
If ExtractDesignSystem.md has not already been run on this project, run it NOW. This primes Claude Design with the app's real tokens and stops it from inventing a competing palette.
Skill("Webdesign") → Workflows/ExtractDesignSystem.md --codebase "$TARGET"3. Compose the Integration Brief
Build a brief that constrains Claude Design to the app's conventions:
TASK: Produce $PROTOTYPE to land at $INTEGRATION_TARGET inside $FRAMEWORK app.
CONSTRAINTS (HARD):
- Use existing tokens from $TARGET/src/styles (do NOT invent new colors)
- Match existing component patterns in $TARGET/src/components
- Follow the app's routing conventions ($FRAMEWORK-specific)
- Preserve these existing files verbatim: $PRESERVE_LIST
INTEGRATION MODE:
- merge (default) — adds new files, modifies minimal existing files
- replace (explicit flag) — allowed to overwrite existing route/component in scope
OUTPUT FORMAT:
- Unified diff (.patch) against the current working tree
- Or: full new files + explicit list of existing files to modify4. Run the Prototype through Framework Translation
Claude Design produces generic output; we need framework-specific code. Use the frontend-design plugin with explicit framework context:
"Translate this Claude Design prototype to $FRAMEWORK conventions used in $TARGET. Use the tokens from $TARGET/tailwind.config.ts. Reuse components from $TARGET/src/components/ui wherever possible."
Output lands in $OUT/translated/.
5. Generate the Diff
Compare translated output against target state:
# Copy target files that will be touched into a staging area
mkdir -p "$OUT/staging"
# ... (list files that would be modified based on the translation)
# Produce the patch
diff -urN "$OUT/staging-original/" "$OUT/staging-new/" > "$OUT/integration.patch"6. Review the Diff
This is the critical human gate. Before applying, show the user:
- Files added: (list)
- Files modified: (list with lines changed)
- Files deleted: (should be empty in merge mode)
- Token conflicts flagged: (any place Claude Design's tokens diverged from app's)
7. Apply the Diff
Only after review approval:
cd "$TARGET"
git checkout -b webdesign-integration-$(date +%Y%m%d)
patch -p1 < "$OUT/integration.patch"8. Verify in Context
Start the app's dev server and navigate to the integrated route:
cd "$TARGET"
bun dev &
DEV_PID=$!
sleep 5
bun ~/.claude/skills/Webdesign/Tools/VerifyDesign.ts "http://localhost:$DEV_PORT$INTEGRATION_TARGET" "$OUT/in-context"
kill $DEV_PIDScreenshot should show the new design rendering correctly inside the app's shell (nav, footer, theme).
9. Run Existing Test Suite
cd "$TARGET"
bun test && bun run typecheck && bun run lintZero regressions. If any test or type-check fails, the integration needs fixes before merging.
10. Hand Back to Caller
The skill returns:
- Branch name with the integration
- Diff summary
- Screenshot of in-context render
- Test/typecheck/lint status
Calling context (blog work, admin panel feature, marketing page) decides whether to merge, iterate, or revert.
Integration Modes
| Mode | When | Effect |
|---|---|---|
| merge (default) | Adding a new page/component/section | Minimal modification of existing files |
| replace | Full redesign of an existing route | Overwrite in scope; existing tokens still respected |
| token-only | Tight hand-off; user will write the code | Only updates tokens.json / tailwind config |
Common Pitfalls
- Skipping the app audit — jumping straight to translation without auditing the target produces code that collides with existing patterns.
- Skipping `ExtractDesignSystem` — Claude Design WILL invent tokens if not primed with the app's real ones. Extract first, always.
- Bypassing the diff review — auto-applying a large diff is how legitimate work gets lost in overwrite. The review gate is not optional.
- Not running tests post-apply — integration can subtly break existing paths (layout regressions, type errors, a11y regressions). The test suite is the safety net.
- Merge into main directly — always use a branch. The user reviews the branch before merging.
Time Estimate
15-45 minutes for a single component/page integration. Complex multi-route integrations: decompose into multiple sessions, one per integration target.
RefinePrototype
Iterate on an existing Claude Design prototype via inline comments, direct text edits, and adjustment knobs.
Trigger Phrases
"iterate on this", "refine the prototype", "adjust spacing", "change the color", "make it more minimal", "smaller hero", "different typography"
Inputs
Required:
- Active Claude Design session — a prototype must already exist in the current claude.ai/design workspace (produced by
CreatePrototypeorWebsiteToRedesign) - Refinement request — free-form text describing the change
Workflow
1. Verify Session
bun ~/.claude/skills/Webdesign/Tools/DriveClaudeDesign.ts screenshot /tmp/current.pngConfirm a live prototype is visible. If not, the session expired or was closed — restart with CreatePrototype.
2. Pick the Refinement Mode
Claude Design supports three refinement modes:
| Mode | When to use | How it lands |
|---|---|---|
| Inline comment | Specific element, surgical change | Click element → comment box → type intent |
| Direct edit | Text content changes, copy tweaks | Click text → edit in place |
| Adjustment knob | Spacing, color, layout, typography scale | Side panel sliders — live, non-destructive |
| Conversational prompt | Structural changes, new sections, different aesthetic | Main chat input |
Use adjustment knobs first (they're fastest and reversible). Fall back to conversational prompts only for structural changes.
3. Apply the Refinement
Adjustment knob (most refinements fit here):
bun ~/.claude/skills/Webdesign/Tools/DriveClaudeDesign.ts adjust --property "spacing" --delta "+20%"
bun ~/.claude/skills/Webdesign/Tools/DriveClaudeDesign.ts adjust --property "primary-color" --value "#2D5A3F"Inline comment:
bun ~/.claude/skills/Webdesign/Tools/DriveClaudeDesign.ts comment --selector "hero-section" --text "Make this 30% shorter and shift the CTA left-aligned"Conversational prompt:
bun ~/.claude/skills/Webdesign/Tools/DriveClaudeDesign.ts prompt "Remove the testimonial section. Add a pricing comparison table above the footer. Keep the current aesthetic."4. Wait for Regeneration
10-45 seconds depending on change scope. Adjustment knobs apply live (<2s); conversational prompts trigger full regeneration.
5. Screenshot and Compare
bun ~/.claude/skills/Webdesign/Tools/DriveClaudeDesign.ts screenshot "$OUT/v${N}.png"Use compare flag for before/after:
bun ~/.claude/skills/Webdesign/Tools/DriveClaudeDesign.ts compare /tmp/current.png "$OUT/v${N}.png"6. Loop or Export
If more refinements needed, repeat from step 2. When satisfied, hand back to CreatePrototype step 6 (Export) or direct to ExportToCode / IntegrateIntoApp.
Refinement Request Patterns
Effective refinement prompts are specific and bounded:
| Good | Bad |
|---|---|
| "Reduce hero padding by 30%" | "Make it tighter" |
| "Use Playfair Display for headings, keep body font" | "Better typography" |
| "Move the CTA button from center to top-right of the hero" | "Fix the CTA" |
| "Shift the palette 20° warmer, keep the same saturation" | "Warmer colors" |
| "Remove the testimonial section entirely" | "Clean it up" |
Common Pitfalls
- Vague refinements — "make it better" produces drift, not improvement. Be specific.
- Too many simultaneous changes — multiple requests in one prompt compound errors. One change at a time for precision.
- Switching aesthetic mid-iteration — see feedback
stay_in_named_workflow.mdin the canonical PAI memory: if you chose brutalist, "make it better" means better-within-brutalist, not switch to minimalist. Be explicit if switching aesthetic. - Refining past the point of return — if 5+ refinement rounds haven't converged, the original brief was wrong. Restart from
CreatePrototypewith a sharper brief.
Time Estimate
30 seconds per adjustment-knob change. 1-2 minutes per conversational refinement. A typical refinement session is 3-7 rounds.
WebsiteToRedesign
Live URL → capture → critique → modernized rebuild via Claude Design.
Trigger Phrases
"redesign this site", "rebuild this page", "modernize this URL", "new look for", "give this site a facelift"
Inputs
Required:
- URL — the live page to capture
- Direction — what should change (aesthetic shift, different audience, new brand, keep-the-bones-fix-the-look)
Optional:
- Preserve list — elements/sections to keep verbatim (copy, structure, specific components)
- Reference sites — 1-3 URLs of sites whose look/feel should inform the rebuild
- Framework target — where the rebuild will land
Workflow
1. Capture the Existing Site
OUT=~/Downloads/webdesign/redesign/$(date +%Y%m%d-%H%M%S)
mkdir -p "$OUT"
# Full-page screenshot
bun ~/.claude/skills/Interceptor/Tools/Open.ts "$URL"
bun ~/.claude/skills/Interceptor/Tools/Screenshot.ts --full-page "$OUT/original.png"
# HTML snapshot
curl -sL "$URL" > "$OUT/original.html"
# Extract tokens from the live site
bun ~/.claude/skills/Webdesign/Tools/VerifyDesign.ts "$URL" "$OUT/original-verify"2. Critique Pass
Before regenerating, do a brief critique. Feed the screenshot + HTML into a quick analysis:
What works on this page? What reads as dated or generic? What is the one thing someone remembers after they leave? What should the rebuild preserve vs rethink?
Write the critique to $OUT/critique.md — Claude Design reads this during the rebuild brief.
3. Compose the Rebuild Brief
Build a brief for Claude Design that references the original:
TASK: Redesign the page currently at $URL.
ORIGINAL SCREENSHOT: (attached) $OUT/original.png
WHAT TO PRESERVE:
$PRESERVE_LIST
AESTHETIC DIRECTION:
$DIRECTION
REFERENCE FEEL (not copy — just mood):
$REFERENCE_SITES
CONSTRAINTS:
- Framework: $FRAMEWORK
- Responsive: mobile-first, breakpoints at 640/768/1024/1280
- Accessibility: WCAG 2.1 AA minimum
- Dark mode: $DARK_MODE_YN
DIFFERENTIATION:
The one memorable element: $DIFFERENTIATOR4. Submit to Claude Design
bun ~/.claude/skills/Webdesign/Tools/DriveClaudeDesign.ts open
bun ~/.claude/skills/Webdesign/Tools/DriveClaudeDesign.ts upload "$OUT/original.png"
bun ~/.claude/skills/Webdesign/Tools/DriveClaudeDesign.ts prompt "$(cat $OUT/brief.md)"5. Iterate
Use RefinePrototype.md workflow for refinements. Common redesign iterations:
- "Keep the headline copy verbatim — only restyle"
- "The hero felt too wide — constrain to max-w-6xl"
- "Bring the testimonial section closer to the features"
- "The CTA color isn't working — try warmer"
6. Side-by-Side Comparison
Before export, capture the new design and compare:
bun ~/.claude/skills/Webdesign/Tools/DriveClaudeDesign.ts screenshot "$OUT/redesigned.png"
bun ~/.claude/skills/Webdesign/Tools/VerifyDesign.ts --compare "$OUT/original.png" "$OUT/redesigned.png" "$OUT/compare"7. Export + Integrate
Based on deployment target:
- Replacing an existing site →
IntegrateIntoApp.md - Greenfield rebuild →
ExportToCode.mdthenDeployDesign.md
Output
$OUT/original.png,$OUT/original.html— captured state$OUT/critique.md— pre-rebuild analysis$OUT/brief.md— Claude Design input$OUT/redesigned.png— final prototype$OUT/compare/— side-by-side- Export artifact (bundle / html / url)
Common Pitfalls
- Skipping the critique — jumping straight to "rebuild this" without analysis produces change without intentionality. The critique makes the rebuild purposeful.
- Preserving too much — if everything is in the preserve list, it's a paint job, not a redesign. Pick 2-4 elements max.
- Using screenshots alone — feeding HTML along with the screenshot gives Claude Design structural context it wouldn't infer from pixels.
- Reference-copying — reference sites inform mood, not content. If the redesign looks like the reference, it failed.
Time Estimate
10-25 minutes end-to-end for a single page. Larger multi-page redesigns: decompose into one session per page.