BXUP Track progress & test yourself — free
Frontend & Mobile · Hands-on lab · ~4 min read
FRONTEND & MOBILE · HANDS-ON LAB

Lab 02 · Figma-to-Component with Fidelity Review

Generate a card-list screen from a design spec, then run a structured fidelity-and-accessibility review against the frame. Five deviations are seeded between the design and the naive output — passing this lab means catching all five, on paper, before any fix.

Arjun leads frontend at an HR-tech company. Design ships Figma frames; engineers ship "pretty close" implementations; the design-QA cycle eats a week per feature. His team just wired the Figma Dev Mode MCP into Cursor, and the first generated screens look right — which is exactly the trap. "Looks right" at a glance and "matches the frame" are different claims, and only one of them survives design review. In this lab you take a fully specified frame, a naive AI-generated implementation, and a structured review sheet — and you learn to review generated UI the way a design-systems engineer does: property by property, not vibe by vibe.

Time & tier

~90 minutes hands-on · Build stage (maps to module M3, playbooks PB-09/PB-11) · $0. If you have Figma Dev Mode MCP access, recreate the frame and drive the pipeline live; on the free tier, use the design-context bundle below — it is the same data the MCP would hand your agent.

Setup

npm create vite@latest fidelity-lab -- --template react-ts
cd fidelity-lab && npm install
mkdir -p src/spec src/naive src/final

The design-context bundle, src/spec/team-directory.frame.md — treat every line as normative, exactly as if it came out of get_design_context:

FRAME: TeamDirectory / MemberCard list  (desktop 1140w)
Layout: vertical stack, gap 24px, page padding 32px
Card: width fill, padding 24px, radius 12px,
      bg #FFFFFF, border 1px #E5E7EB,
      shadow none at rest; hover: shadow 0 4px 12px rgba(15,23,42,0.08)
Card content: horizontal auto-layout, gap 16px, align center
  Avatar: 48x48 circle; image, or initials fallback on #EEF2FF,
          initials color #4F46E5, weight 600
  Text stack: gap 4px
    Name: 18px / weight 600 / #0F172A
    Role: 14px / weight 400 / #64748B
  Spacer: fill
  Status pill: padding 4px 12px, radius 999px, 12px / weight 500
    Active  -> text #166534 on #DCFCE7
    On leave-> text #92400E on #FEF3C7
Interaction: entire card clickable -> member profile.
  Must be reachable and operable by keyboard; visible focus ring
  2px #4F46E5 offset 2px.
Accessibility notes (from design annotations):
  - Avatar images are decorative alongside the visible name
  - Pill must not rely on color alone (text label carries meaning)

The naive output an agent produced from a screenshot of this frame, src/naive/MemberCard.tsx. Copy it verbatim — do not fix anything while typing:

type Member = {
  id: string;
  name: string;
  role: string;
  avatarUrl?: string;
  status: "active" | "on-leave";
};

export function MemberCard({ member, onOpen }: { member: Member; onOpen: (id: string) => void }) {
  return (
    <div
      onClick={() => onOpen(member.id)}
      style={{
        display: "flex", alignItems: "center", gap: 16,
        padding: 16, borderRadius: 12, background: "#FFFFFF",
        border: "1px solid #E2E8F0", cursor: "pointer",
      }}
    >
      {member.avatarUrl && (
        <img src={member.avatarUrl} alt={member.name} width={48} height={48}
             style={{ borderRadius: "50%" }} />
      )}
      <div style={{ display: "flex", flexDirection: "column", gap: 4 }}>
        <span style={{ fontSize: 16, fontWeight: 600, color: "#0F172A" }}>{member.name}</span>
        <span style={{ fontSize: 14, color: "#64748B" }}>{member.role}</span>
      </div>
      <div style={{ flex: 1 }} />
      <span
        style={{
          padding: "4px 12px", borderRadius: 999, fontSize: 12, fontWeight: 500,
          color: member.status === "active" ? "#166534" : "#92400E",
          background: member.status === "active" ? "#DCFCE7" : "#FEF3C7",
        }}
      >
        {member.status === "active" ? "Active" : "On leave"}
      </span>
    </div>
  );
}

The review instrument, review-sheet.md — you will fill one row per property:

| # | Property (from frame)     | Frame value | Implementation value | Match? | Severity |
|---|---------------------------|-------------|----------------------|--------|----------|
Sections to cover: layout & spacing / color & border / typography /
states & variants (hover, focus, fallbacks) / interaction & keyboard /
accessibility annotations
Severity scale: BLOCK (fails design or a11y intent) / FIX (visible drift) / NIT

Guided steps

  1. Render the naive card first. Wire MemberCard into App.tsx with three members — one active with an avatar URL, one on leave, one active without an avatar URL — and npm run dev. Expected: three cards render; note anything that strikes you immediately, but write nothing on the sheet yet. First impressions are the "vibe review" you are about to beat.
  2. Do the property-by-property pass. Go through team-directory.frame.md line by line. For every specified value — gaps, padding, radius, border color, each type size and weight, each pill color — add a row to review-sheet.md with the frame value and the implementation's actual value (read the code, and verify in DevTools computed styles where the code is ambiguous). Expected: 15–20 rows. This should take you 20 minutes; slower is fine, skipping rows is not.
  3. Test the states the screenshot couldn't show. Hover a card: does the specified shadow appear? Tab through the page: can you reach a card, does a focus ring show, does Enter open it? Load the avatar-less member: what renders? Each answer becomes a row. A screenshot-driven generation pipeline is systematically blind to hover, focus, and fallback states — this step is where that blindness becomes visible.
  4. Audit against the accessibility annotations. Two annotations sit at the bottom of the frame spec. Check each against the code and add rows. Also run a quick screen-reader sanity pass (VoiceOver: Cmd-F5) over one card: what does it announce, and would a non-mouse user know the card is interactive?
  5. Tally and classify. Mark each non-matching row BLOCK / FIX / NIT. Expected: you have found at least five real deviations. If you have fewer than five, return to step 3 — the seeds concentrate in states and interaction, not in resting pixels.
  6. Fix in src/final/MemberCard.tsx. Copy the naive file and repair every BLOCK and FIX row. Use semantic elements for interactivity, real fallbacks, and the exact frame values. Re-run the hover/keyboard/fallback checks from step 3 against your fix.
  7. Run the multimodal cross-check. Screenshot your rendered final card and paste it into a vision-capable assistant with the frame spec text: "List every visual discrepancy between this implementation and this spec — spacing, type, color, radius, states." Expected: it confirms your fixes and possibly flags 1–2 nits you missed. Record whether it found anything your manual pass did not, and whether it hallucinated any discrepancy that does not exist — both numbers go in your notes.
  8. Write the review-sheet verdict. One paragraph: would you approve the naive PR? Which deviation classes would automated pixel-diffing have caught, and which needed a human or an interaction test?
Note

Keep the naive file untouched. The deliverable of a fidelity review is the sheet, and the sheet is only auditable if the artifact it reviewed still exists.

Verify

  • review-sheet.md has 15+ property rows, each with a concrete frame value and a concrete implementation value — no "looks ok" entries.
  • All five seeded deviations appear on the sheet with severity assigned (check yourself against the answer key only after tallying).
  • src/final/MemberCard.tsx renders correctly for: hover, keyboard focus + Enter, missing avatar, both statuses.
  • The step-7 vision pass is logged with two counts: discrepancies it caught that you missed, and discrepancies it invented.
  • Your verdict paragraph names at least one deviation class that pixel-diffing alone would never catch.

Self-grading rubric (pass = 7/10)

  • Seed detection (0–4): all five seeds on the sheet before reading the key = 4; four = 3; three = 2.
  • Review rigor (0–2): every sheet row cites concrete values on both sides = 2.
  • Fix quality (0–2): final card passes hover, keyboard, and fallback checks with frame-exact values = 2.
  • Tool calibration (0–2): vision-pass results recorded with both counts, and your verdict correctly assigns deviation classes to detection methods = 2.
▸Answer key — the five seeded deviationsoptional deep dive — click to expand

The naive output was seeded with exactly five deviations from the frame. Expected findings:

  • Card padding: 16 instead of 24. The frame says padding 24px; the code says padding: 16. Severity FIX. The classic screenshot-estimation error — proportions survive, absolute spacing doesn't. Pixel diffing against a design render catches this.
  • Name typography: 16/600 instead of 18/600. Frame: Name: 18px / weight 600; code: fontSize: 16. Severity FIX. Type-scale drift is the most common Figma-to-code deviation class in the wild.
  • Border color: #E2E8F0 instead of #E5E7EB. Two near-identical grays — invisible at a glance, unambiguous in the sheet. Severity NIT-to-FIX. This seed exists to prove why the review is property-by-property: no human eyeballs a 3-point gray delta.
  • Missing states: no hover shadow, no avatar-initials fallback. The frame specifies a hover elevation and an initials fallback on #EEF2FF; the code has neither — the avatar-less member renders with no avatar at all. Severity BLOCK. Screenshot-driven generation cannot see states that weren't in the screenshot; your prompt or review has to carry them.
  • Interaction/a11y: clickable div, no keyboard path, wrong alt semantics. The card is a div onClick — unreachable by Tab, no role, no focus ring (frame specifies one), Enter does nothing. Additionally the avatar sets alt={member.name} while the design annotation marks it decorative next to the visible name (should be alt=""), causing double-announcement in a screen reader. Severity BLOCK. The correct fix is a semantic interactive element (a real <a>/<button> or role="link" with tabIndex, key handling, and the specified focus ring).

Scoring notes: the pill colors are correct — if you flagged them, that's a false positive; count it against review rigor. Deviation classes by detection method (step 8): padding, type size, border color → pixel diff catches; hover/fallback states and keyboard/a11y → only interaction testing and annotation-aware human review catch them. The step-7 vision model typically confirms the first three and misses the last two unless you explicitly ask about states — which is itself the lesson.

Key Takeaway:

Design-to-code fidelity is a property-by-property claim, not an impression — generated UI drifts hardest exactly where screenshots are blind (states, interaction, semantics), so your review sheet must interrogate what the frame says, not what the render shows.

This is the free reading view. Open BXUP to track your progress, run the hands-on labs, and prove it with original, exam-pattern assessments.
© Anvi Innovate · BXUP — Be Exponentially Upskilled · BXUP is an independent learning platform, not affiliated with or endorsed by AWS, Microsoft, Google, NVIDIA, IBM, Salesforce, Databricks, Oracle or Anthropic. All trademarks belong to their respective owners. · bxup.in