Use this when a design is approved and engineers need clear specifications to build it. Produces annotated handoff documentation covering behavior specs, responsive rules, interaction notes, accessibility requirements, and acceptance criteria mapping. Bridges the gap between "looks good in Figma" and "built correctly in code."
Related skills: Run
/design-reviewbefore handoff to catch issues. Use/component-specfor reusable component documentation. Feed acceptance criteria into/story-write.
Process
Step 1: Gather inputs
Ask the user to provide:
- The approved design -- Figma link, screenshots, or prototype URL
- The story or feature spec -- acceptance criteria, PRD section, or feature description
- Design system -- component library or style guide the team uses (if any)
- Target platforms -- web (responsive?), iOS, Android, or all
- Breakpoints -- the team's responsive breakpoint definitions (or use defaults: 320, 768, 1024, 1440)
Step 2: Component inventory
List every distinct component visible in the design:
## Component Inventory -- {{feature name}}
| Component | Existing in DS? | Variant | Notes |
|-----------|----------------|---------|-------|
| (e.g., DataTable) | Yes / No / Modified | (e.g., sortable, paginated) | (customizations needed) |
Flag components that don't exist in the design system -- these need new component specs or custom implementation.
Step 3: Behavior specifications
For each interactive element, document what happens:
## Behavior Specs
### {{Component or interaction name}}
**Trigger:** (What the user does -- click, hover, focus, swipe, type)
**Action:** (What the system does in response)
**Feedback:** (What the user sees/hears -- animation, state change, message, sound)
**Duration:** (Animation timing if applicable -- e.g., 200ms ease-out)
**Reversibility:** (Can the user undo this? How?)
#### State transitions
| From state | User action | To state | Visual change |
|------------|------------|----------|---------------|
| Default | Hover | Hover | (describe change) |
| Default | Focus | Focused | (describe focus ring) |
| Default | Click | Active/Loading | (describe transition) |
| Loading | Complete | Success | (describe confirmation) |
| Loading | Failure | Error | (describe error display) |
Step 4: Responsive behavior
## Responsive Specifications
| Breakpoint | Layout change | Components affected | Notes |
|------------|--------------|--------------------|----- |
| < 320px | (minimum supported -- what breaks?) | | |
| 320-767px (mobile) | (layout description) | (components that change) | (stack, hide, reorder) |
| 768-1023px (tablet) | (layout description) | (components that change) | |
| 1024-1439px (desktop) | (layout description) | (components that change) | |
| 1440px+ (wide) | (layout description -- max-width or fluid?) | | |
Step 5: Accessibility notes
## Accessibility Requirements
| Element | Requirement | Implementation |
|---------|------------|----------------|
| (interactive element) | Keyboard operable | Tab order: (specify), Enter/Space to activate |
| (dynamic content) | Screen reader announcement | aria-live="polite" for (state change) |
| (form field) | Error association | aria-describedby linking to error message |
| (modal/dialog) | Focus management | Trap focus, return to trigger on close |
| (images) | Alt text | (specific alt text or aria-hidden if decorative) |
Step 6: Content and copy specifications
## Copy Specs
| Element | Copy | Character limit | Dynamic? | Truncation rule |
|---------|------|----------------|----------|-----------------|
| (heading) | (exact text) | (limit) | Yes/No | (ellipsis, wrap, tooltip) |
| (CTA) | (exact text) | (limit) | No | N/A |
| (error message) | (exact text) | (limit) | Yes -- (variable) | (rule) |
Step 7: Compile and present
Combine all sections into a single handoff document. Ask the user:
- Are there interactions I missed?
- Does the team need more detail on any component?
- Should I generate stories from these specs using
/story-write? - Any platform-specific considerations I should add?
Output location
Present the handoff document as formatted text in the conversation. For team use, export to Notion, Confluence, or attach to the relevant stories in the project tracker.
Example Output
Input
- Approved design: Figma file for "Guest Checkout Flow" – 3-screen flow: Cart Summary → Payment Entry → Order Confirmation; includes mobile and desktop frames
- Feature spec: PRD section 4.2 – "Allow unauthenticated users to complete purchase without creating an account; capture email for order tracking; AC includes inline validation on all payment fields, error recovery within same screen, and confirmation email trigger"
- Design system: Meridian DS (internal React component library, v2.3) with tokens for color, typography, and spacing
- Target platforms: Responsive web
- Breakpoints: 375, 768, 1280, 1600
Output
Design Handoff — Guest Checkout Flow
Feature: Guest Checkout (PRD §4.2) | Design version: Approved 2024-01-18 | Prepared for: Sprint 11 Engineering
Component Inventory — Guest Checkout Flow
| Component | Existing in DS? | Variant | Notes |
|---|---|---|---|
| CartSummaryPanel | Yes | Collapsible | Collapsed by default on mobile only |
| TextInput | Yes | Default, Error, Disabled | Needs new card-number variant with PAN masking |
| CreditCardInput | No | — | Custom implementation needed; see component spec |
| SelectDropdown | Yes | Default | Used for expiry month/year — chain-dependent behavior |
| PrimaryButton | Yes | Default, Loading, Disabled | Loading state not yet in DS v2.3 — needs addition |
| InlineErrorMessage | Yes | Field-level | Reused as-is |
| OrderConfirmationBanner | No | — | New component; full-width, success green, animated entry |
| ProgressIndicator | Modified | 3-step, no labels on mobile | DS version has labels at all breakpoints — suppress below 768px |
⚠️ 2 new components required: CreditCardInput, OrderConfirmationBanner — run /component-spec before sprint kickoff.
Behavior Specs
Payment Form — Field Validation
Trigger: User removes focus (blur) from any payment input field Action: Client-side validation runs against field ruleset Feedback: Red border + inline error message appears below field; field label turns error-red Duration: 150ms ease-in for border color transition; error message fades in at 200ms Reversibility: Error clears on next valid input (live validation after first blur)
State transitions
| From state | User action | To state | Visual change |
|---|---|---|---|
| Default | Focus | Focused | Blue $color-focus-ring border (2px) |
| Focused | Blur (invalid) | Error | Red border, error message appears below |
| Error | Type valid input | Correcting | Error message hides; border returns to focused blue |
| Correcting | Blur (valid) | Valid | Green checkmark icon appears inline right |
| Valid | Blur | Valid | Checkmark persists; no border |
Place Order Button
Trigger: User clicks "Place Order" Action: Form submission; payment tokenization API call fires Feedback: Button transitions to Loading state (spinner replaces label text); all fields disabled Duration: Spinner appears immediately; no timeout — resolves on API response Reversibility: No cancel affordance during loading (per design decision); back-button navigates to cart
State transitions
| From state | User action | To state | Visual change |
|---|---|---|---|
| Default | Hover | Hover | $color-primary-dark background |
| Default | Click (form valid) | Loading | Label replaces with 20px spinner; button width locked |
| Default | Click (form invalid) | Error summary | Scroll-to-top; all invalid fields highlighted simultaneously |
| Loading | API success | — | Full-page transition to Order Confirmation screen |
| Loading | API failure | Error | Toast notification (top-center, 4s); button resets to Default |
Responsive Specifications
| Breakpoint | Layout change | Components affected | Notes |
|---|---|---|---|
| < 375px | Not officially supported; content scrollable, no horizontal scroll | All | Minimum tested at 320px; no layout breakage expected |
| 375–767px (mobile) | Single column; CartSummaryPanel collapsed to accordion at top; payment form full-width below | CartSummaryPanel, ProgressIndicator | ProgressIndicator shows dots only, no step labels |
| 768–1279px (tablet) | Two-column: cart left (40%), payment form right (60%); CartSummaryPanel always expanded | CartSummaryPanel, ProgressIndicator | ProgressIndicator shows step labels |
| 1280–1599px (desktop) | Two-column: cart left (35%), payment form right (65%); max-width 1200px centered | All | Order summary sticky within left column on scroll |
| 1600px+ (wide) | Same as desktop; container max-width 1200px, outer background fills viewport | Layout container | No fluid stretching beyond 1200px |
Accessibility Requirements
| Element | Requirement | Implementation |
|---|---|---|
| Card Number field | Input purpose declared | autocomplete="cc-number"; inputmode="numeric" |
| Expiry selects | Grouped label | Wrap month + year in <fieldset> with <legend>Expiration date</legend> |
| Inline error messages | Programmatically associated | Each error <span> has unique ID; input has aria-describedby pointing to it |
| Place Order button (Loading) | State communicated | aria-busy="true" during loading; aria-label="Processing your order" replaces visible label |
| Error summary (form-invalid submit) | Focus management | Focus moves to summary container div[role="alert"] at top of form on invalid submit |
| Order Confirmation banner | Announcement | aria-live="assertive" on banner container; fires once on mount |
| CartSummaryPanel accordion | Keyboard operable | Toggle via Enter/Space; aria-expanded reflects state |
| Progress indicator | Non-interactive context | aria-label="Step 2 of 3: Payment" on container; steps not focusable |
Copy Specs
| Element | Copy | Character limit | Dynamic? | Truncation rule |
|---|---|---|---|---|
| Page heading | "Secure Checkout" | 20 | No | N/A |
| Place Order CTA | "Place Order — ${{total}}" | 28 | Yes — order total | Never truncate; reflow if needed |
| Card number placeholder | "1234 5678 9012 3456" | — | No | N/A |
| Generic API error toast | "Payment couldn't be processed. Please try again or use a different card." | 80 | No | Wrap to 2 lines max in toast |
| Confirmation heading | "You're all set, {{first_name}}!" | 35 | Yes — first name or "there" if unavailable | N/A |
| Confirmation subtext | "Order #{{order_id}} confirmation sent to {{email}}" | 60 | Yes — order ID, email | Truncate email at 25 chars with tooltip showing full |
| Email field label | "Email address (for order updates)" | 40 | No | N/A |
Open Questions for Team
- CreditCardInput masking: Should PAN be masked after 4 chars during entry (e.g.,
•••• •••• •••• 1234) or only after blur? Design shows post-blur masking — confirm with security team. - Express checkout options (Apple Pay / Google Pay) appear in Figma desktop frames but are not in PRD §4.2 scope — include or defer?
- Loading button width: Figma shows button locks at current width during loading. Confirm this should be
min-widthlocked via JS or CSS-only.
Next steps:
- Run
/component-specforCreditCardInputandOrderConfirmationBanner - Run
/story-writeto generate tickets from the acceptance criteria above - Assign
PrimaryButtonLoading state addition to DS team before sprint start