---
title: "Design To Code"
description: "Mockup-to-component pipeline using Google Stitch, 21st.dev, and Storybook MCP. Accepts a screenshot, a description, or a URL and produces production-ready React components, checking existing Storybook components before generating anything new. Use when implementing UI from a mockup or screenshot. To call the MCP tool surface on its own, with no design to convert, use storybook-mcp-integration."
canonical: "https://orchestkit.yonyon.ai/docs/reference/skills/design-to-code"
---

# Design To Code

Mockup-to-component pipeline using Google Stitch, 21st.dev, and Storybook MCP. Accepts a screenshot, a description, or a URL and produces production-ready React components, checking existing Storybook components before generating anything new. Use when implementing UI from a mockup or screenshot. To call the MCP tool surface on its own, with no design to convert, use storybook-mcp-integration.

<span className="badge badge-blue">Command</span> 

```bash title="Invoke"
/ork:design-to-code
```

<ContextualSkillSidebar slug="design-to-code" />

> **Design To Code** Mockup-to-component pipeline using Google Stitch, 21st.dev, and Storybook MCP. Accepts a screenshot, a description, or a URL and produces production-ready React components, checking existing Storybook components before generating anything new. Use when implementing UI from a mockup or screenshot. To call the MCP tool surface on its own, with no design to convert, use storybook-mcp-integration.


# Design to Code

Convert visual designs into production-ready React components using a four-stage pipeline: Extract, Match, Adapt, Render.

```bash
/ork:design-to-code screenshot of hero section    # From description
/ork:design-to-code /tmp/mockup.png               # From screenshot
/ork:design-to-code https://example.com/pricing    # From URL
```

## Pipeline Overview

```
Input (screenshot/description/URL)
  │
  ▼
┌─────────────────────────┐
│ Stage 1: EXTRACT         │  Stitch MCP → HTML + design context
│ build_site               │  Generate up to 5 screens from prompt
│ get_screen_code / _image │  Extract React/HTML + PNG for each
└─────────┬───────────────┘
          │
          ▼
┌─────────────────────────┐
│ Stage 2: MATCH           │  1. Storybook MCP → check existing
│ Storybook-first lookup   │  2. 21st.dev → search public registry
│ Then 21st.dev fallback   │  3. Filesystem → grep codebase
└─────────┬───────────────┘
          │
          ▼
┌─────────────────────────┐
│ Stage 3: ADAPT           │  Merge extracted design + matched
│ Apply project tokens     │  components into final implementation
│ Customize to codebase    │  Tests + types included
└─────────┬───────────────┘
          │
          ▼
┌─────────────────────────┐
│ Stage 4: RENDER          │  Register as json-render catalog entry
│ Generate Zod schema      │  Same component → PDF, email, video
│ Add to defineCatalog()   │  Multi-surface reuse via MCP output
└─────────┬───────────────┘
          │
          ▼
┌─────────────────────────┐
│ Stage 4b: VERIFY         │  Storybook MCP → self-healing loop
│ run-story-tests(a11y)    │  Fix violations, retry (max 3)
│ preview-stories          │  Embed live preview in chat
└─────────────────────────┘
```

## Argument Resolution

```python
INPUT = ""  # Full argument string
# Detect input type:
# - Starts with "/" or "~" or contains ".png"/".jpg" → screenshot file path
# - Starts with "http" → URL to screenshot or live page
# - Otherwise → natural language description
```

## Step 0: Detect Input Type and Project Context

```python
# 1. Create main task IMMEDIATELY
TaskCreate(subject="Design to code: {INPUT}", description="Four-stage pipeline: extract, match, adapt, render", activeForm="Converting design to code")

# 2. Create subtasks for each stage
TaskCreate(subject="Extract design context", activeForm="Extracting design context")               # id=2
TaskCreate(subject="Match components (Storybook-first)", activeForm="Matching components")         # id=3
TaskCreate(subject="Adapt to project tokens and conventions", activeForm="Adapting to project")    # id=4
TaskCreate(subject="Register in json-render catalog", activeForm="Registering in catalog")         # id=5
TaskCreate(subject="Verify with Storybook self-healing", activeForm="Verifying component")         # id=6

# 3. Set dependencies for sequential stages
TaskUpdate(taskId="3", addBlockedBy=["2"])  # Match needs extracted design context
TaskUpdate(taskId="4", addBlockedBy=["3"])  # Adapt needs matched components
TaskUpdate(taskId="5", addBlockedBy=["4"])  # Render needs adapted component
TaskUpdate(taskId="6", addBlockedBy=["5"])  # Verify needs rendered component

# 4. Update status as you progress
TaskUpdate(taskId="2", status="in_progress")  # When starting
TaskUpdate(taskId="2", status="completed")    # When done — repeat for each subtask

# Detect project's design system
Grep("@theme", glob="**/*.css")   # Tailwind v4: theme lives in CSS, not a config file
Glob("**/tailwind.config.*")      # Tailwind v3 only (v4 ignores this file)
Glob("**/tokens.css")
Glob("**/.tokens.json")
# Read existing tokens if found → used in Stage 3

# Detect shadcn/ui style (v4 style system)
Glob("**/components.json")
# Read → style field (e.g., "radix-luma", "base-nova")
# Determines class names: Luma=rounded-4xl, Nova=compact, Lyra=sharp
# Store: SHADCN_STYLE for Stage 2 filtering + Stage 3 adaptation
```

## Stage 1: Extract Design Context

**If stitch MCP is available:**
```python
# Official Stitch MCP tools (stitch.withgoogle.com/docs/mcp):
#   - build_site(prompt)          → multi-screen app, up to 5 interconnected screens
#   - get_screen_code(screenId)   → returns React/HTML for a generated screen
#   - get_screen_image(screenId)  → returns PNG of a generated screen
#
# For screenshot/URL input:
#   1. Upload screenshot to Stitch (or pass URL)
#   2. Call build_site() with the visual as context
#   3. get_screen_code() to retrieve the React/HTML output
#
# For description input:
#   1. Call build_site(prompt=<description>)
#   2. get_screen_code() / get_screen_image() to retrieve the result
#
# DESIGN.md import (Stitch Pro, Mar 2026+):
#   Stitch can also import a natural-language DESIGN.md file to regenerate
#   a layout without starting from scratch. Useful for iterative edits.
```

**If stitch MCP is NOT available (fallback):**
```python
# For screenshot: Read the image file directly (Claude is multimodal)
# Analyze layout, colors, typography, spacing from the image
# For URL: WebFetch the page, extract HTML structure
# For description: Skip extraction, proceed to Stage 2 with description
```

> **Resolution budget (Opus 5 / CC 2.1.111+):** Mockups up to **2,576 px on the long edge** (~3.75 MP, 3× prior ceiling) produce better component boundaries and spacing extraction. Full-page desktop mockups at native resolution are now in-budget; previously they had to be resized down and lost fine detail. Only downscale inputs exceeding 2,576 px.

Extract and produce:
- Color palette (hex/oklch values)
- Typography (font families, sizes, weights)
- Spacing patterns (padding, margins, gaps)
- Component structure (headers, cards, buttons, etc.)
- Layout pattern (grid, flex, sidebar, etc.)

## Stage 2: Match Components (Storybook-First)

**Priority 1 — Check project's own Storybook (if storybook-mcp available):**
```python
# Search the project's existing component library first
inventory = list-all-documentation()  # Full component + docs manifest
for component in inventory.components:
    if component matches extracted_description:
        details = get-documentation(id=component.id)
        # Returns: props schema, stories, usage patterns
        # → Existing component found — skip external search
```

**Priority 2 — Search 21st.dev (if no Storybook match and 21st-dev-magic available):**
```python
# Search 21st.dev for matching components
# Use the component descriptions from Stage 1
# Example: "animated pricing table with toggle"
# Filter: React, Tailwind CSS, shadcn/ui compatible
# If SHADCN_STYLE detected, prefer components matching style's visual language
# (e.g., Luma → rounded/pill-shaped, Nova → compact/dense, Lyra → sharp/boxy)
```

**Priority 3 — Filesystem fallback (if no MCP servers available):**
```python
# Search for components in the project's existing codebase
Grep(pattern="export.*function|export.*const", glob="**/*.tsx")
# Check for shadcn/ui components
Glob("**/components/ui/*.tsx")
# Generate from scratch if no matches found
```

Present matches to user:
```python
AskUserQuestion(questions=[{
  "question": "Which component approach for {component_name}?",
  "header": "Component",
  "options": [
    {"label": "Reuse from Storybook", "description": "{existing_component} — props: {prop_list}"},
    {"label": "Use 21st.dev match", "description": "{matched_component_name} — {match_score}% match"},
    {"label": "Adapt from codebase", "description": "Modify existing {existing_component}"},
    {"label": "Generate from scratch", "description": "Build new component from extracted design"}
  ],
  "multiSelect": false
}])
```

## Stage 3: Adapt to Project

Merge the extracted design context with matched/generated components:

1. **Apply project tokens** — Replace hardcoded colors/spacing with project's design tokens
2. **Apply shadcn style classes** — If `SHADCN_STYLE` detected, use style-correct class names:
   - Luma: `rounded-4xl` buttons/cards, `shadow-md` + `ring-1 ring-foreground/5`, `gap-6 py-6`
   - Nova: compact `px-2 py-1`, reduced margins, tight spacing
   - Lyra: `rounded-none`, sharp edges, monospace-friendly
   - Mira: ultra-dense, minimal padding
3. **Match naming conventions** — Follow project's component naming patterns
4. **Add TypeScript types** — Full type safety with Zod validation for any data props
5. **Include tests** — MSW handlers for API-backed components, render tests for static
6. **Responsive** — Mobile-first with breakpoints matching project's system

### Output Structure
```
src/components/
  └── {ComponentName}/
      ├── {ComponentName}.tsx       # Main component
      ├── {ComponentName}.test.tsx  # Tests
      └── index.ts                 # Barrel export
```

## Stage 4: Register in json-render Catalog

After ADAPT produces a working React component, register it as a json-render catalog entry for multi-surface reuse.

1. **Generate Zod schema** — Derive a Zod schema from the component's TypeScript props
2. **Add catalog entry** — Register in the project's `defineCatalog()` call with props schema and children declaration
3. **Verify rendering** — Confirm the component renders correctly through `&lt;Render catalog=\{catalog\} /&gt;` path

```typescript
import { z } from 'zod'

// Zod schema derived from {ComponentName}Props
const componentSchema = z.object({
  title: z.string().max(100),
  variant: z.enum(['default', 'featured']).default('default'),
  // ... props from the adapted component
})

// Add to project catalog
import { defineCatalog } from '@json-render/core'
import { existingCatalog } from './catalog'

export const catalog = {
  ...existingCatalog,
  {ComponentName}: {
    props: componentSchema,
    children: true, // or false for leaf components
  },
}
```

**Enables:** same component output to PDF, email, video, or MCP response — no reimplementation needed.

**Skip condition:** If the project has no json-render dependency or catalog, inform the user and skip catalog registration. The component from Stage 3 is still fully usable standalone.

### Stage 4b: Self-Healing Verification (if storybook-mcp available)

After generating the component, verify it with Storybook MCP:

```python
# 1. Write CSF3 story for the new component
Write("src/components/{Name}/{Name}.stories.tsx", story_code)

# 2. Run tests via MCP (component + a11y)
results = run-story-tests(
    stories=[{ "storyId": "{name}--default" }],
    a11y=True
)

# 3. Handle failures — self-heal up to 3 attempts
if not results.all_passed:
    for failure in results.failures:
        # Read violation details, fix the component
        Edit("src/components/{Name}/{Name}.tsx", fix)
    # Re-run failing tests
    results = run-story-tests(stories=[...], a11y=True)

# 4. Preview — embed live story in chat for user confirmation
previews = preview-stories(stories=[
    { "absoluteStoryPath": "src/components/{Name}/{Name}.stories.tsx",
      "exportName": "Default" }
])
# Include preview URLs in response for visual confirmation
```

**Skip condition:** If storybook-mcp is not available, skip verification. The component is still usable — just not auto-verified.

## Graceful Degradation

| stitch | 21st-dev-magic | storybook-mcp | Behavior |
|--------|----------------|---------------|----------|
| Available | Available | Available | Full pipeline: extract + Storybook-first match + adapt + render + self-heal verify |
| Available | Available | Unavailable | Extract + 21st.dev match + adapt + render (no verification) |
| Available | Unavailable | Available | Extract + Storybook match + adapt + verify |
| Unavailable | Available | Available | Description-based search + Storybook check + adapt + verify |
| Unavailable | Unavailable | Available | Filesystem search + Storybook verify only |
| Unavailable | Unavailable | Unavailable | Manual analysis + generate from scratch (still works) |

The skill ALWAYS produces output regardless of MCP availability. Storybook MCP adds component reuse (Stage 2) and self-healing verification (Stage 4b) — skipping them still yields a working component.

## Anti-Patterns

- **NEVER** output components with hardcoded colors — use design tokens
- **NEVER** skip TypeScript types — all props must be typed
- **NEVER** generate without checking existing project patterns first
- **NEVER** ignore the project's existing component library structure

## Quality Bar

Done means all of these hold:
- Generated components carry no hardcoded colors or literal spacing — every visual value resolves through project design tokens.
- Every prop is TypeScript-typed; data-bearing props carry Zod validation.
- Stage 2 checked existing components (Storybook, then 21st.dev, then filesystem) before generating from scratch, and the chosen match source is named.
- When storybook-mcp is available, the component passes `run-story-tests` (component + a11y) before handoff, self-healing within at most 3 attempts.
- Output follows the `\{ComponentName\}/` structure — component, test, and barrel export.
- Each skipped stage (json-render catalog, Storybook verify) is taken ONLY when its dependency is absent, and the skip is stated with its reason.

## Related Skills

- `storybook-mcp-integration` — Storybook MCP tools: component discovery, testing, previews
- `component-search` — Search 21st.dev registry standalone
- `design-context-extract` — Extract design DNA from screenshots
- `/ork:design-stylecards` — named aesthetic recipes (shadow stacks, glass surfaces, gradients, type scales) for the polish pass on generated components
- `design-system-tokens` — Token architecture and management
- `storybook-testing` — CSF3 patterns, Vitest integration, Chromatic TurboSnap
- `json-render-catalog` — Catalog definition, Zod schemas, defineCatalog patterns
- `multi-surface-render` — Render catalog entries to PDF, email, video, MCP


---

## Rules (1)

### json-render Catalog Registration — MEDIUM


## json-render Catalog Registration

After Stage 3 ADAPT produces a working React component, register it in the project's json-render catalog so the same component can render to PDF, email, video, or MCP output without reimplementation.

### When to Register

- The project has `@json-render/core` as a dependency
- A `defineCatalog()` call exists in the project (typically `src/catalog.ts` or `lib/catalog.ts`)
- The component has stable, typed props suitable for AI-driven rendering

If none of these conditions are met, skip catalog registration and inform the user.

### How to Generate a Zod Schema from Component Props

**Incorrect:**
```typescript
// Skipping catalog registration — component is stuck in one surface
// Only usable as a direct React import, cannot be rendered to PDF/email/video
export function PricingCard({ plan, price, features }: PricingCardProps) {
  return <div>...</div>
}
// No Zod schema, no catalog entry — dead end for multi-surface
```

**Correct:**
```typescript
import { z } from 'zod'

// 1. Derive Zod schema from the component's TypeScript props
export const PricingCardSchema = z.object({
  plan: z.enum(['free', 'pro', 'enterprise']),
  price: z.string().max(20),
  features: z.array(z.string().max(100)).min(1).max(10),
  highlighted: z.boolean().default(false),
})

// 2. Component implementation uses the same schema type
export type PricingCardProps = z.infer<typeof PricingCardSchema>

export function PricingCard({ plan, price, features, highlighted }: PricingCardProps) {
  return <div>...</div>
}
```

### How to Add to the Project's defineCatalog() Call

**Incorrect:**
```typescript
// Manual object spread loses runtime validation
const catalog = {
  ...existingComponents,
  PricingCard: { component: PricingCard },
}
```

**Correct:**
```typescript
import { existingCatalog } from './catalog'
import { PricingCardSchema, PricingCard } from './components/PricingCard'

export const catalog = {
  ...existingCatalog,
  PricingCard: {
    props: PricingCardSchema,
    children: false, // leaf component — no nested children
  },
}

// For layout components that wrap other catalog entries:
export const catalogWithLayout = {
  ...existingCatalog,
  FeatureSection: {
    props: FeatureSectionSchema,
    children: true, // accepts any catalog children
  },
}
```

### Key Rules

- Always use `.max()` bounds on strings and arrays to prevent unbounded AI generation
- Use `z.enum()` for any prop with a finite set of valid values
- Set `children: false` for data-display components, `children: true` for layout wrappers
- Compose with object spread (`\{ ...base, ...custom \}`); `@json-render/core` exports no catalog-merge helper
- Export the Zod schema alongside the component for reuse in tests and type generation
