⚡ Promptolis Original · Coding & Development
🎯 Figma Design Spec Writer
Reads your Figma design + writes the engineering spec: component structure, prop names, edge cases, accessibility notes, the 7 ambiguities engineers will ask about, and the design tokens to use.
Why this is epic
Most design-to-engineering handoffs fail because the spec is in the designer's head and the design file. Engineers ship the wrong empty state, wrong loading state, wrong responsive breakpoints — then the cycle of 'this isn't right' Slack messages begins. This Original writes the spec engineers actually need.
Outputs the FULL spec engineers want: component name + props + variants, behavior on edge cases (empty, loading, error, success, disabled), responsive behavior, accessibility, design tokens to reference, and the 7 specific ambiguities the engineer will ask about (preempt them).
Calibrated to 2026 design-eng workflows: design-token systems, component libraries, Figma Dev Mode, code generation tools (v0, Builder.io). Picks the right format for YOUR team's workflow — not a generic spec template.
Includes the 'what's missing from the design' section — designs always under-specify edge cases, and surfacing the gaps before handoff saves 3-5 follow-up rounds.
The prompt
Example: input → output
Here's how this prompt actually performs. Real input below, real output from Claude Opus 4.
<design-context>Figma frame 'Customer Onboarding — Step 3 — Plan Selection.' I'll describe: 3 plan cards in a horizontal row on desktop. Each card has a plan name (Starter / Growth / Enterprise), a price, a list of 5-7 features with checkmarks, a CTA button ('Choose Starter' etc.). The middle card (Growth) has a 'MOST POPULAR' badge and is visually emphasized (blue border, slightly elevated shadow). Below the cards: a 'Continue' button (disabled until a plan is selected) and a 'Skip for now' link.</design-context>
<spec-purpose>Page/feature spec for the plan-selection step in our onboarding flow</spec-purpose>
<framework>Next.js 14 + React + TypeScript + Tailwind CSS</framework>
<component-library>shadcn/ui (have Card, Button, Badge components) + custom internal library (have OnboardingStepShell, OnboardingNav)</component-library>
<design-tokens>Tailwind config + CSS variables. Token names like color-brand-primary, color-text-primary, spacing-md, radius-lg, shadow-elevated, font-size-h2, etc. Defined in /lib/design-tokens.ts</design-tokens>
<states-to-cover>Empty (no plan selected yet), one-card-selected, all states for the Continue button, hover states, focus states for keyboard nav, error state if API fails when committing selection</states-to-cover>
<responsive-requirements>Mobile (<640px): cards stack vertically. Tablet (640-1024px): cards in 1 column with reduced padding. Desktop (>1024px): 3 cards in a row.</responsive-requirements>
<accessibility-baseline>WCAG 2.1 AA. We have a screen-reader-tested baseline; new components must maintain.</accessibility-baseline>
<engineer-context>2 mid-level React engineers + 1 junior. Junior is new to the codebase. Team uses AI-assisted coding (Cursor) so spec should be detailed enough to give to AI.</engineer-context>
Spec: Plan Selection Step (Onboarding Step 3)
Spec Summary
This is the third step of customer onboarding. The user selects one of three plans (Starter, Growth, Enterprise) and proceeds. The middle plan (Growth) is marked as 'MOST POPULAR' to nudge selection. After selection, user clicks Continue. Skip-for-now is available but rarely chosen. On success, advance to step 4. On error, show inline error and allow retry.
Key product behavior:
- Plan selection is required to advance (Continue is disabled until a plan is selected)
- Plan selection is committed to the user's account on Continue, not on selection (lets users change mind freely before committing)
- The 'Skip for now' option creates a free-tier account; clarifies on click
Component Architecture
<OnboardingStepShell title="Choose your plan" stepNumber={3} totalSteps={5}>
<PlanSelection
plans={PLANS}
selectedPlanId={selectedPlanId}
onSelect={handleSelect}
onContinue={handleContinue}
onSkip={handleSkip}
isCommitting={isCommitting}
error={commitError}
/>
</OnboardingStepShell>
Sub-components:
<PlanCard plan={plan} isSelected={...} isMostPopular={...} onSelect={...} />- Reuses shadcn
<Card>,<Button>,<Badge> - Reuses internal
<OnboardingStepShell>(handles overall layout, progress indicator, back button) - Reuses internal
<OnboardingNav>(Continue + Skip footer)
Props API
interface PlanSelectionProps {
plans: Plan[]; // exactly 3 expected; component handles 1-N gracefully but design assumes 3
selectedPlanId: string | null;
onSelect: (planId: string) => void;
onContinue: () => Promise<void>;
onSkip: () => void;
isCommitting?: boolean; // disables Continue + cards while committing
error?: string | null; // inline error message above Continue
}
interface Plan {
id: string;
name: 'Starter' | 'Growth' | 'Enterprise' | string;
price: { monthly: number; annual: number; currency: string }; // display strategy in Visual Design
features: string[]; // 5-7 expected
isMostPopular?: boolean;
ctaLabel?: string; // defaults to `Choose ${name}`
}
interface PlanCardProps {
plan: Plan;
isSelected: boolean;
isDisabled?: boolean; // during commit
onSelect: () => void;
}
Variant Matrix
| Variant / State | Visual difference |
|---|---|
| PlanCard — default unselected | Standard border (color-border-default), shadow-sm, full-color CTA button |
| PlanCard — selected | Brand-color border (color-brand-primary), shadow-elevated, CTA button shows checkmark + 'Selected' label |
| PlanCard — Most Popular (Growth) | 'MOST POPULAR' badge (yellow Badge component) above card, 1px brand-color border by default, slightly larger shadow, all other behaviors same |
| PlanCard — disabled (during commit) | Cursor: not-allowed, opacity 50%, click handler removed |
| PlanCard — hover | Shadow-elevated, border subtly elevated to color-border-hover, slight transform Y -2px (transition 150ms ease) |
| PlanCard — focus (keyboard) | 2px brand-color outline + 2px outline-offset, no shadow change |
| Continue button — empty (no plan) | Disabled, color-text-muted on color-bg-muted, cursor: not-allowed |
| Continue button — ready | Full brand-color, color-text-on-brand text, cursor: pointer |
| Continue button — committing | Spinner + 'Saving...' text, disabled |
| Error banner | Red text + icon, color-error background at 10% opacity, full-width above Continue |
Visual Design
Layout (Desktop ≥1024px)
Container max-width: 960px (centered). Padding: spacing-xl horizontal, spacing-2xl vertical.
Plan cards: display: grid; grid-template-columns: 1fr 1fr 1fr; gap: spacing-md.
Cards align to top. Middle card (Growth) — when isMostPopular — has a wrapper that adds spacing-sm above for the badge.
Footer (Continue + Skip): flex; justify-content: space-between; align-items: center; margin-top: spacing-xl.
Layout (Tablet 640-1024px)
Cards stack vertically. Single column. Cards full-width within container. Reduce internal padding from spacing-lg → spacing-md to fit better.
Layout (Mobile <640px)
Same as tablet (vertical stack). Container padding reduced to spacing-md horizontal.
Footer buttons stack vertically: Continue (full-width primary) above Skip (text link).
Card Internal Structure
<Card> (radius-lg, padding spacing-lg)
<Badge> 'MOST POPULAR' </Badge> [conditional, color-brand-yellow background]
<Heading h3 font-size-h3> {plan.name} </Heading>
<PriceRow>
<Span font-size-h2 font-weight-bold> ${plan.price.monthly} </Span>
<Span font-size-body color-text-muted> /mo, billed annually </Span>
</PriceRow>
<FeatureList> [spacing-sm vertical gaps]
<Feature> ✓ {feature1} </Feature>
... (5-7 features)
</FeatureList>
<Button>
{isSelected ? '✓ Selected' : `Choose ${plan.name}`}
</Button>
</Card>
Typography
- Plan name: font-size-h3 (24px), font-weight-bold
- Price (large number): font-size-h2 (32px), font-weight-bold
- Price suffix ('/mo'): font-size-body (16px), color-text-muted
- Features: font-size-body (16px)
- 'MOST POPULAR' badge: font-size-xs (12px), font-weight-semibold, uppercase, letter-spacing-wide
Colors (token names)
- Card border default: color-border-default
- Card border selected: color-brand-primary
- Card border most-popular (default state): color-brand-primary at 50% opacity → full opacity when selected
- Card background: color-bg-elevated (white in light mode, color-gray-900 in dark mode)
- Text: color-text-primary; muted text: color-text-muted
- Badge background: color-warning-bg; badge text: color-warning-fg
- Continue button (active): color-brand-primary background, color-text-on-brand foreground
- Continue button (disabled): color-bg-muted background, color-text-muted foreground
- Error banner: color-error-bg (at 10% opacity) background, color-error-fg text
Spacing
- Card internal padding: spacing-lg (24px)
- Card-to-card gap: spacing-md (16px)
- Element vertical rhythm inside card: spacing-sm (8px) between feature list items, spacing-md between sections
- Footer top margin: spacing-xl (32px)
Behavior Specifications
Empty state (no plan selected — default on first render)
- All cards in unselected variant
- Continue button: disabled, label 'Continue'
- Skip link: enabled
- Error: none shown
One-card-selected
- Selected card visually distinct (variant: selected)
- Other cards remain in unselected variant
- Continue button: enabled
- User can change selection by clicking another card
Committing (after Continue clicked)
- All cards: disabled variant (no clicks)
- Continue button: shows spinner + 'Saving...'
- Skip link: disabled
- On success: parent advances to step 4 (this component does not handle navigation)
- On error: re-enable cards, show error banner, log error to monitoring
Error state
- Error banner appears above Continue button: 'Something went wrong. Please try again.'
- Continue button re-enables (allowing retry)
- Cards re-enable
- Error clears on next click of any card OR Continue
Skip
- On Skip click: open confirmation modal (out of scope — handled by OnboardingNav; mention in implementation notes)
- On confirm: parent handles routing to free-tier-onboarding step
Responsive Behavior
| Breakpoint | Layout | Notes |
|---|---|---|
| Mobile <640px | Cards stack vertical, full-width | Footer buttons stack vertical, Continue full-width above Skip text-link |
| Tablet 640-1024px | Cards stack vertical, full-width | Footer buttons remain horizontal |
| Desktop ≥1024px | 3 cards in row, equal width | As described in Visual Design |
Card order on mobile/tablet: Starter → Growth → Enterprise (matches desktop left-to-right). Growth's 'MOST POPULAR' badge stays at top of its card.
Accessibility
Color contrast
- Body text on card background: min 4.5:1 (color-text-primary on color-bg-elevated)
- Badge text on badge bg: min 4.5:1 (verify color-warning combination)
- CTA button text on button bg: min 4.5:1 (verify color-text-on-brand on color-brand-primary)
- Disabled states: maintain min 3:1 (acknowledged lower bar for disabled UI)
Focus order (keyboard tab sequence)
1. Starter card (entire card is focusable as a button)
2. Growth card
3. Enterprise card
4. Continue button
5. Skip link
All cards focusable via Tab. Selection via Enter or Space. Arrow keys move between cards (use roving tabindex pattern).
ARIA
- Card root:
role="button",aria-pressed={isSelected},aria-label="{plan.name} plan, ${plan.price.monthly} per month, {features.length} features" - 'MOST POPULAR' badge: visible label is sufficient — but ensure it's read via
aria-label="Most popular plan"on the parent card - Error banner:
role="alert"so screen readers announce on appearance - Continue button: standard button semantics
Semantic HTML
- Cards as
<button>elements (NOT divs with click handlers) — gets focus, keyboard, screen-reader behavior for free - Feature list as
<ul>/<li>
Keyboard navigation
- Tab: cycles through cards + Continue + Skip
- Arrow keys (left/right): move selection between cards (good UX, not strictly required for AA)
- Enter / Space: select card
- Escape: no specific behavior (no modals here)
Edge Cases
- Very long plan name (>20 chars): truncate with ellipsis, full name in tooltip on hover. Mobile: allow 2 lines, no truncation.
- Very long feature (>40 chars): allow up to 2 lines per feature with proper line-height. Don't truncate features — they're load-bearing for the decision.
- Fewer than 5 features: card just renders shorter; no layout adjustment needed
- More than 7 features: design limit; if data has 8+, render first 7 + 'and more — see plan details' link
- Annual vs monthly toggle: not in this design but likely future-state. Spec the prop interface but no UI yet.
- All plans isMostPopular=true (data error): render only the first as Most Popular, log warning
- Dark mode: required. Token system handles automatically. Verify:
- Card background: color-bg-elevated maps to color-gray-900 in dark mode
- Borders: color-border-default lightens slightly
- Shadows: reduce intensity (shadows show less on dark backgrounds)
- RTL support: layout flips automatically via Tailwind's rtl plugin if used. Card order: rightmost = Starter in RTL. Verify in QA.
The 7 Ambiguities Engineers Will Ask About
1. 'What if the API to commit selection takes >3s? Should there be a timeout?'
- Yes — 8s timeout. After timeout, show error banner with 'Request timed out — please try again.'
2. 'Should the Continue button be disabled DURING commit, even after success?'
- During commit: disabled. After success: parent component handles navigation; this component doesn't matter.
3. 'What happens if the user clicks Continue twice rapidly?'
- Second click is no-op (button is disabled during commit). Implement via local isCommitting state.
4. 'On mobile, should the Most Popular card still be 'middle' (i.e., second in vertical stack)?'
- Yes. Order is fixed Starter → Growth → Enterprise regardless of breakpoint. The badge does the visual emphasis.
5. 'Should card click toggle selection (click selected → deselect)?'
- No. Click on already-selected card is no-op. To change selection, click a different card. Design intent is single-select, not toggle.
6. 'Where does the price display strategy come from? Annual / monthly?'
- Default to monthly price ('$X /mo, billed annually'). Annual-only display is future scope. The Plan type supports both for forward compat.
7. 'What about loading state on initial render — when plans are being fetched?'
- Out of scope for this component. Parent (OnboardingStepShell or its parent page) handles initial fetch + loading skeleton. This component receives plans already loaded.
What's Missing from the Design
1. Error state visual — design shows happy path. Spec defines: red banner above Continue, 8px from top of footer.
2. Loading state during commit — design shows static. Spec defines: spinner replaces button label.
3. Disabled card visual during commit — design doesn't show. Spec defines: 50% opacity, no hover.
4. Tooltip on truncated plan names — design doesn't show. Spec defines: native browser tooltip via `title` attribute.
5. Dark mode tokens — assumed via token system. Designer should verify in Figma if dark mode designs exist.
6. Skip confirmation modal — design references but doesn't show. Spec assumes OnboardingNav handles; flag as separate spec item.
7. Touch-target size on mobile — cards are touch-targets; ensure min 44×44px touch target (likely already met by card size, but verify).
Design Tokens to Use
Verify these exist in /lib/design-tokens.ts:
Colors:
- color-brand-primary, color-brand-yellow
- color-text-primary, color-text-muted, color-text-on-brand
- color-border-default, color-border-hover
- color-bg-elevated, color-bg-muted
- color-warning-bg, color-warning-fg
- color-error-bg, color-error-fg
Spacing:
- spacing-sm (8px), spacing-md (16px), spacing-lg (24px), spacing-xl (32px), spacing-2xl (48px)
Radius:
- radius-lg
Shadow:
- shadow-sm, shadow-elevated
Typography:
- font-size-xs, font-size-body, font-size-h3, font-size-h2
- font-weight-bold, font-weight-semibold
- letter-spacing-wide
If any of these don't exist, flag in pre-implementation review.
Test Cases
Use Playwright (or RTL for unit). Cover:
1. Initial render: all 3 cards visible, none selected, Continue disabled.
2. Click Starter card: Starter card shows selected variant, Continue enables, other cards remain unselected.
3. Click Growth (already 'MOST POPULAR'): Growth shows selected variant.
4. Change selection: click Starter then click Enterprise — Enterprise selected, Starter unselected.
5. Click Continue → success: isCommitting flips true; on success, parent navigation triggers (assert navigation).
6. Click Continue → API error: error banner appears, cards re-enable, Continue re-enables.
7. Keyboard: Tab to Starter → Space to select → Tab to Continue → Enter to commit.
8. Mobile viewport: cards stack vertically; Continue is full-width.
9. A11y: axe-core passes; screen reader announces card selection state.
10. Long plan name truncation: plan with name 'Enterprise Plus Premium' truncates with title tooltip.
Implementation Notes
- Don't use
<div onClick>for cards. Use<button>so keyboard + screen-reader work for free. - shadcn
<Card>doesn't acceptonClickdirectly. Wrap in a button or apply onClick at outer wrapper. - Tailwind's group-hover utilities are useful for hover variants but verify with focus-visible for keyboard parity.
- Avoid framer-motion for the hover transform. Tailwind's transition classes are sufficient and lighter.
- isCommitting should be component state, not derived from props. Parent passes onContinue (async); this component awaits + manages local state.
- Junior engineer note: the roving-tabindex pattern for arrow-key navigation is a common confusion. If team isn't familiar, default to standard Tab navigation (each card is focusable in tab order) and skip arrow-key support — still WCAG-compliant.
- AI-assisted coding note: when prompting Cursor/v0, paste the full spec, not just the summary. AI tools produce better output with complete state matrices and ARIA specs.
Key Takeaways
- The 7 ambiguities section is where this spec earns its keep. Without preempting, expect 3-5 follow-up rounds of 'what should happen when X.'
- Cards as
<button>elements (not divs with onClick). Keyboard + screen-reader behavior for free. - Token names, not hex codes. The spec must reference
color-brand-primary, not#3B82F6. - Define ALL states (empty, loading, committing, error, success) — design typically only shows happy path.
- Mobile-first responsive specification. Define ≥1024 last, not first.
- Run an a11y check (axe-core or Lighthouse) before merge. WCAG AA is the minimum bar.
Common use cases
- Designer handing off a feature to engineering and wanting fewer 'this isn't right' rounds
- Engineer reading a design and wanting to ask better questions before starting (or generating the spec themselves)
- Design lead establishing a handoff template the team uses every time
- Solo founder doing both design and engineering and writing specs for an outsourced dev team
- Agency PM creating handoff docs for client projects
- Product team migrating to a new component library and re-spec'ing existing designs
Best AI model for this
Claude Opus 4. Design-spec writing requires reasoning about component abstractions, state spaces, and edge cases — exactly Claude's strengths. ChatGPT GPT-5 second-best. Multimodal models (paste Figma screenshots) outperform link-only models.
Pro tips
- Paste a screenshot of the Figma frame, not just the URL. Multimodal models can read the design directly; URL-only loses the visual.
- Specify your design-token system if you have one. 'color/text/primary' is more useful in the spec than '#1F2937'. The spec should reference tokens.
- Always include states (empty, loading, error, success, disabled) explicitly in the request. Designs usually only show happy path; the spec must define all states.
- If you have a component library, list it. Don't re-spec components that exist in the library — reference them by name.
- Responsive specifications usually need 3 breakpoints minimum (mobile <640, tablet 640-1024, desktop >1024). If your design only shows desktop, the spec must define the rest.
- Accessibility is non-negotiable in 2026. Color contrast, focus order, screen-reader labels, keyboard navigation. Specs without these get pushback in code review.
- The 7-ambiguities-to-preempt section is where this Original earns its keep. Engineers asking 'what should happen when X' is the slowest part of handoff.
Customization tips
- Paste a screenshot of the Figma frame, not just the URL. Multimodal Claude can read the design directly. URL-only specs are weaker.
- Specify your design-token system precisely — token names matter for the spec to be useful. If you don't have tokens, the Original will recommend a token-naming scheme.
- List your component library by name AND key components. The spec references existing components rather than re-spec'ing them.
- If junior engineers will implement, mention it. The spec includes more implementation notes for less-experienced engineers.
- Specify which states matter explicitly. Some features genuinely don't need an 'error state' (read-only views); others need every state.
- For mobile-app specs, use the Mobile App Spec Mode variant — it adds platform conventions for iOS/Android that web specs skip.
Variants
Component Spec Mode
For specifying a single reusable component (Button, Card, Modal) — emphasizes prop API, variant matrix, and library integration.
Page/Feature Spec Mode
For specifying a full page or feature — emphasizes component composition, data fetching, navigation, page-level states.
Mobile App Spec Mode
For native iOS/Android specs — adds platform conventions, native-only states, gestures, and platform component mapping.
Dev Mode Output
Optimized for Figma Dev Mode + code-generation tools — produces specs that copy/paste cleanly into v0, Builder.io, or component-generation prompts.
Frequently asked questions
How do I use the Figma Design Spec Writer prompt?
Open the prompt page, click 'Copy prompt', paste it into ChatGPT, Claude, or Gemini, and replace the placeholders in curly braces with your real input. The prompt is also launchable directly in each model with one click.
Which AI model works best with Figma Design Spec Writer?
Claude Opus 4. Design-spec writing requires reasoning about component abstractions, state spaces, and edge cases — exactly Claude's strengths. ChatGPT GPT-5 second-best. Multimodal models (paste Figma screenshots) outperform link-only models.
Can I customize the Figma Design Spec Writer prompt for my use case?
Yes — every Promptolis Original is designed to be customized. Key levers: Paste a screenshot of the Figma frame, not just the URL. Multimodal models can read the design directly; URL-only loses the visual.; Specify your design-token system if you have one. 'color/text/primary' is more useful in the spec than '#1F2937'. The spec should reference tokens.
Explore more Originals
Hand-crafted 2026-grade prompts that actually change how you work.
← All Promptolis Originals