Claude Code Plugins

Community-maintained marketplace

Feedback

mockup-extraction

@marcusgoll/Spec-Flow
16
0

Extract reusable components from approved HTML mockups during implementation. Identifies patterns, maps CSS to Tailwind, and populates prototype-patterns.md for visual fidelity. Use at start of /implement for UI-first features.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name mockup-extraction
description Extract reusable components from approved HTML mockups during implementation. Identifies patterns, maps CSS to Tailwind, and populates prototype-patterns.md for visual fidelity. Use at start of /implement for UI-first features.
The mockup-extraction skill guides Claude in analyzing approved HTML mockups and extracting reusable components for implementation.

Purpose: Bridge the gap between visual mockups and production code by:

  1. Identifying repeated UI patterns across mockup screens
  2. Mapping mockup CSS classes to Tailwind utilities
  3. Documenting component variants and states
  4. Creating extraction tasks for systematic implementation

When to invoke: At the start of /implement phase when mockups exist and are approved (UI-first features).

Before extraction, verify: 1. Mockups exist in `specs/NNN-slug/mockups/` or `design/prototype/screens/` 2. Mockup approval gate passed in state.yaml 3. Theme files exist (theme.yaml, theme.css) 1. **Inventory** - List all mockup HTML files 2. **Scan** - Parse HTML for component patterns 3. **Classify** - Categorize by component type 4. **Count** - Track occurrence frequency 5. **Map** - Convert CSS to Tailwind utilities 6. **Document** - Populate prototype-patterns.md 7. **Generate Tasks** - Create extraction tasks for tasks.md Generate `prototype-patterns.md` in feature directory:
# Prototype Patterns: {FEATURE_NAME}

## Component Inventory
| Component | Source Screens | Occurrences | Priority |
|-----------|---------------|-------------|----------|
| Button Primary | login, signup, dashboard | 12 | Must extract |
| Card | dashboard, settings | 8 | Must extract |
| Form Input | login, signup, profile | 15 | Must extract |
| Alert | all screens | 6 | Consider extraction |

## Component Details

### Button Primary
- **CSS Classes**: `.btn`, `.btn-primary`
- **Tailwind**: `px-4 py-2 bg-primary text-white rounded-md hover:bg-primary-hover`
- **States**: default, hover, focus, disabled, loading
- **Props**: `variant`, `size`, `icon`, `loading`

### Card
- **CSS Classes**: `.card`, `.card-elevated`
- **Tailwind**: `bg-surface p-4 rounded-lg shadow-md`
- **Variants**: default, elevated, bordered
- **Props**: `padding`, `shadow`, `header`, `footer`

## CSS to Tailwind Mapping
| CSS Variable | Tailwind Utility |
|--------------|------------------|
| var(--color-primary) | bg-primary, text-primary |
| var(--space-4) | p-4, m-4, gap-4 |
| var(--radius-md) | rounded-md |
| var(--shadow-md) | shadow-md |

## Visual Fidelity Checklist
- [ ] Spacing matches 8pt grid
- [ ] Colors use design tokens
- [ ] All states implemented (hover, focus, disabled)
- [ ] Typography uses scale
- [ ] Responsive breakpoints match
## Component Identification Rules

Scan mockup HTML for these patterns:

Buttons

Selectors: button, [role="button"], .btn, .button
Variants: primary, secondary, outline, ghost, danger
States: default, hover, focus, active, disabled, loading

Cards

Selectors: .card, [role="article"], .panel, .tile
Variants: default, elevated, bordered, interactive
Components: header, body, footer, actions

Forms

Selectors: form, .form-group, .form-field, .input-group
Elements: input, textarea, select, checkbox, radio
States: default, focus, error, disabled, readonly

Alerts/Notifications

Selectors: .alert, [role="alert"], .toast, .notification
Variants: info, success, warning, error
Components: icon, message, dismiss button

Navigation

Selectors: nav, .nav, .navbar, .sidebar, .menu
Elements: links, dropdowns, icons, badges
States: active, hover, expanded

Data Display

Selectors: table, .table, .list, .grid
Elements: header, rows, cells, pagination
States: loading, empty, error

Modals/Dialogs

Selectors: .modal, [role="dialog"], .dialog, .drawer
Components: backdrop, header, body, footer, close button
States: open, closing
## Reusability Scoring

Score components by occurrence frequency:

Occurrences Score Action
1 Low Inline styles OK
2 Medium Consider extraction
3+ High Must extract
5+ Critical Extract with variants

Priority order for extraction:

  1. Components appearing on 5+ screens (Critical)
  2. Components with 3+ variants (High)
  3. Interactive components with multiple states (High)
  4. Layout components (containers, grids) (Medium)
  5. Static display components (Medium)
## CSS to Tailwind Mapping

Color Mappings

var(--color-primary) → bg-primary, text-primary, border-primary
var(--color-secondary) → bg-secondary, text-secondary
var(--color-neutral-*) → bg-neutral-*, text-neutral-*
var(--color-success) → bg-success, text-success
var(--color-error) → bg-error, text-error

Spacing Mappings (8pt grid)

var(--space-1) → p-1, m-1, gap-1 (8px)
var(--space-2) → p-2, m-2, gap-2 (16px)
var(--space-3) → p-3, m-3, gap-3 (24px)
var(--space-4) → p-4, m-4, gap-4 (32px)
var(--space-6) → p-6, m-6, gap-6 (48px)
var(--space-8) → p-8, m-8, gap-8 (64px)

Typography Mappings

var(--text-xs) → text-xs
var(--text-sm) → text-sm
var(--text-base) → text-base
var(--text-lg) → text-lg
var(--text-xl) → text-xl
var(--text-2xl) → text-2xl
var(--font-semibold) → font-semibold
var(--font-bold) → font-bold

Border & Shadow Mappings

var(--radius-sm) → rounded-sm
var(--radius-md) → rounded-md
var(--radius-lg) → rounded-lg
var(--radius-full) → rounded-full
var(--shadow-sm) → shadow-sm
var(--shadow-md) → shadow-md
var(--shadow-lg) → shadow-lg

Layout Mappings

display: flex → flex
display: grid → grid
flex-direction: column → flex-col
justify-content: center → justify-center
align-items: center → items-center
gap: var(--space-*) → gap-*
## Extraction Output Format

For each extracted component, document:

### Component: {NAME}

**Source Screens**: {list of mockup files}
**Occurrences**: {count}
**Priority**: {Must extract | Consider | Low}

**HTML Structure**:
```html
<button class="btn btn-primary">
  <span class="btn-icon">{icon}</span>
  <span class="btn-label">{label}</span>
</button>

CSS Classes:

  • .btn - Base button styles
  • .btn-primary - Primary variant
  • .btn-icon - Icon container
  • .btn-label - Label text

Tailwind Equivalent:

<button className="inline-flex items-center gap-2 px-4 py-2 bg-primary text-white rounded-md hover:bg-primary-hover focus:ring-2 focus:ring-primary/30 disabled:opacity-50">
  {icon && <span>{icon}</span>}
  <span>{label}</span>
</button>

States to Implement:

  • Default
  • Hover (bg-primary-hover)
  • Focus (ring-2 ring-primary/30)
  • Active (scale-95)
  • Disabled (opacity-50, cursor-not-allowed)
  • Loading (animate-spin icon)

Props Interface:

interface ButtonProps {
  variant: 'primary' | 'secondary' | 'outline' | 'ghost';
  size: 'sm' | 'md' | 'lg';
  icon?: React.ReactNode;
  loading?: boolean;
  disabled?: boolean;
  children: React.ReactNode;
}
</extraction_output_format>

<tailwind_variants_output>
## tailwind-variants (tv) Output Format

When `tailwind-variants` is detected in the project (or recommended for new projects), generate tv() component definitions:

### Single-Part Component (Button)

```typescript
// components/ui/button.ts
import { tv, type VariantProps } from 'tailwind-variants';

export const button = tv({
  base: 'inline-flex items-center justify-center gap-2 rounded-md font-medium transition-all duration-100 focus:outline-none focus:ring-2 focus:ring-offset-2',
  variants: {
    variant: {
      primary: 'bg-primary text-white hover:bg-primary-hover focus:ring-primary/30',
      secondary: 'bg-secondary text-white hover:bg-secondary-hover focus:ring-secondary/30',
      outline: 'border border-primary text-primary bg-transparent hover:bg-primary/10',
      ghost: 'text-primary bg-transparent hover:bg-primary/10',
      danger: 'bg-error text-white hover:bg-error-hover focus:ring-error/30'
    },
    size: {
      sm: 'px-3 py-1.5 text-sm',
      md: 'px-4 py-2 text-base',
      lg: 'px-6 py-3 text-lg'
    },
    disabled: {
      true: 'opacity-50 cursor-not-allowed pointer-events-none'
    },
    loading: {
      true: 'pointer-events-none'
    }
  },
  compoundVariants: [
    {
      loading: true,
      class: 'relative text-transparent'
    }
  ],
  defaultVariants: {
    variant: 'primary',
    size: 'md'
  }
});

export type ButtonVariants = VariantProps<typeof button>;

Multi-Part Component with Slots (Card)

// components/ui/card.ts
import { tv, type VariantProps } from 'tailwind-variants';

export const card = tv({
  slots: {
    base: 'rounded-lg bg-surface overflow-hidden',
    header: 'px-4 py-3 border-b border-neutral-200',
    body: 'px-4 py-4',
    footer: 'px-4 py-3 border-t border-neutral-200 bg-neutral-50',
    title: 'text-lg font-semibold text-neutral-900',
    description: 'text-sm text-neutral-500'
  },
  variants: {
    variant: {
      default: { base: 'border border-neutral-200' },
      elevated: { base: 'shadow-md' },
      bordered: { base: 'border-2 border-neutral-300' }
    },
    padding: {
      sm: { body: 'px-3 py-2' },
      md: { body: 'px-4 py-4' },
      lg: { body: 'px-6 py-6' }
    }
  },
  defaultVariants: {
    variant: 'default',
    padding: 'md'
  }
});

export type CardVariants = VariantProps<typeof card>;

Component Usage

// Usage in React component
import { button, type ButtonVariants } from '@/components/ui/button';
import { card } from '@/components/ui/card';

// Button
<button className={button({ variant: 'primary', size: 'lg' })}>
  Click me
</button>

// Card with slots
const { base, header, body, title } = card({ variant: 'elevated' });
<div className={base()}>
  <div className={header()}>
    <h3 className={title()}>Card Title</h3>
  </div>
  <div className={body()}>Content here</div>
</div>

Form Input with Compound Variants

// components/ui/input.ts
import { tv } from 'tailwind-variants';

export const input = tv({
  base: 'w-full rounded-md border bg-surface px-3 py-2 text-base transition-colors focus:outline-none focus:ring-2',
  variants: {
    variant: {
      default: 'border-neutral-300 focus:border-primary focus:ring-primary/30',
      error: 'border-error focus:border-error focus:ring-error/30',
      success: 'border-success focus:border-success focus:ring-success/30'
    },
    size: {
      sm: 'px-2 py-1 text-sm',
      md: 'px-3 py-2 text-base',
      lg: 'px-4 py-3 text-lg'
    },
    disabled: {
      true: 'bg-neutral-100 cursor-not-allowed opacity-60'
    }
  },
  compoundVariants: [
    {
      variant: 'error',
      disabled: false,
      class: 'text-error'
    }
  ],
  defaultVariants: {
    variant: 'default',
    size: 'md'
  }
});
## Brownfield Project Integration

When extracting components for existing codebases, perform gap analysis first.

Detection Phase

Check for existing component libraries:

# Detect from package.json
COMPONENT_LIB=$(jq -r '
  if .dependencies["@radix-ui/react-dialog"] then "radix"
  elif .dependencies["@chakra-ui/react"] then "chakra"
  elif .dependencies["@mui/material"] then "mui"
  elif .dependencies["tailwind-variants"] then "tv-ready"
  else "custom"
  end
' package.json 2>/dev/null || echo "none")

# Detect shadcn/ui (check for components/ui directory)
if [ -d "src/components/ui" ] || [ -d "components/ui" ]; then
  COMPONENT_LIB="shadcn"
fi

Gap Analysis Output

Generate gap analysis in prototype-patterns.md:

## Gap Analysis: Mockup vs Existing Components

### Brownfield Context
- **Component Library**: shadcn/ui
- **Design Tokens**: tailwind.config.js
- **Variant API**: tailwind-variants installed

### Component Comparison

| Mockup Component | Existing Match | Integration Mode | Gap Details |
|-----------------|----------------|------------------|-------------|
| Button (5 variants) | button.tsx (3 variants) | **Extend** | +ghost, +danger variants |
| Card (elevated) | card.tsx (flat only) | **Extend** | +elevated, +bordered variants |
| Avatar | ❌ None | **Create** | New component needed |
| Alert (4 types) | toast.tsx (2 types) | **Wrap** | Different API, wrap for compat |
| Modal | dialog.tsx | **Map** | Same function, different name |
| Badge | badge.tsx | **Use** | Exact match |

### Integration Modes

#### Extend (add variants to existing)
```typescript
// Existing: button.tsx has primary, secondary, outline
// Add: ghost, danger variants

// Option 1: Extend tv() definition
export const button = tv({
  extend: existingButton,  // Import existing
  variants: {
    variant: {
      ghost: 'text-primary bg-transparent hover:bg-primary/10',
      danger: 'bg-error text-white hover:bg-error-hover'
    }
  }
});

// Option 2: Merge variants
export const button = tv({
  ...existingButton.config,
  variants: {
    ...existingButton.config.variants,
    variant: {
      ...existingButton.config.variants.variant,
      ghost: '...',
      danger: '...'
    }
  }
});

Wrap (different API, create compatibility layer)

// Existing: toast.tsx uses different props
// Mockup: Alert with variant="info|success|warning|error"

import { toast } from '@/components/ui/toast';

export const alert = tv({
  base: 'p-4 rounded-lg border',
  variants: {
    variant: {
      info: 'bg-info/10 border-info text-info',
      success: 'bg-success/10 border-success text-success',
      warning: 'bg-warning/10 border-warning text-warning',
      error: 'bg-error/10 border-error text-error'
    }
  }
});

// Wrapper component uses alert tv() but can fall back to toast

Create (no existing component)

// No existing Avatar component - create new
export const avatar = tv({
  base: 'relative inline-flex shrink-0 overflow-hidden rounded-full',
  variants: {
    size: {
      sm: 'h-8 w-8',
      md: 'h-10 w-10',
      lg: 'h-12 w-12',
      xl: 'h-16 w-16'
    }
  },
  defaultVariants: { size: 'md' }
});

Map (same function, document alias)

// Existing: dialog.tsx
// Mockup: calls it "Modal"
// Action: Create alias export

export { dialog as modal } from './dialog';
export type { DialogProps as ModalProps } from './dialog';

### Extraction Task Priority (Brownfield)

| Priority | Integration Mode | Action |
|----------|-----------------|--------|
| P0 | Create | New components (no existing match) |
| P1 | Extend | Add missing variants to existing |
| P2 | Wrap | Create compatibility layer |
| P3 | Map | Document alias only |

### Skip List

Components to skip extraction (exact match exists):

```yaml
skip_extraction:
  - badge        # Exact match in components/ui/badge.tsx
  - separator    # Exact match in components/ui/separator.tsx
  - skeleton     # Exact match in components/ui/skeleton.tsx
## Validation Checklist

After extraction, verify:

Pattern Coverage

  • All repeated components identified
  • Variant analysis complete
  • State documentation complete
  • Props interface defined

Tailwind Mapping

  • All CSS variables mapped
  • No hardcoded values remain
  • Responsive classes documented
  • Dark mode considerations noted

Visual Fidelity

  • Spacing follows 8pt grid
  • Colors use theme tokens
  • Typography uses scale
  • Shadows and borders consistent
## Related Resources