# Figma Survey

> Survey a Figma design against what a Vaadin app already builds, interview the user in rounds on every divergence, and write the project's design spec. Ends in the spec plus a delta in the conversation — never in code, theme CSS, or a ticket.

- Skill: `vaadin/figma-survey` (Agent Skill, multi-file: 4 files)
- Install (CLI): `npx skillmds@latest add vaadin/figma-survey`
- Raw SKILL.md: https://api.skillmd.com/api/skills/vaadin/figma-survey/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Design & Media
- Author: vaadin (https://skillmd.com/u/vaadin)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/vaadin/figma-survey

---


# Figma survey

A survey reads two things — the design, and whatever the app already builds for it — and
writes two:

- **The design spec**, the durable half: what the design asks for, with a decision
  recorded wherever the design and the app disagree. A later run looks a value up here
  instead of deciding it again, which is the whole point — an agent with no memory of the
  last session invents a plausible value rather than reaching for the settled one, and by
  the fifth view the app reads as several products.
- **The delta**, the transient half: what the design shows that the app does not yet do,
  written checkable so each item can become a ticket's acceptance-criteria line.

It writes no application code, no theme CSS, and **no ticket**. Applying the spec belongs
to `figma-theme` (the global theme) or to implementation (a view). Filing the delta belongs
to a separate ticketing pass, run on this conversation once the user has read the delta.
The survey ends in the conversation, and that pause is what lets the expensive judgement
calls be reviewed before a ticket is worded or a diff is started.

Applies to Vaadin Flow (Java) on the Aura theme.

## Two rules that shape every step

**Facts are yours; decisions are the user's.** Anything readable from the design, the code
or the existing spec, you look up. Anything that picks one side over the other, you put to
the user in **rounds**, each question carrying your recommendation — the method is
[`interview.md`](interview.md). The spec is written only once the interview's **frontier**
is empty.

**A survey never boots the app.** Its three inputs — the design, the code, the existing
spec — are all readable without one, and a running app can only show the scale you are
about to replace. A figure only a browser produces (a contrast ratio, a rendered pixel
size, a resolved token) is left marked **unverified** for whoever applies the spec. An
unverified row said to be unverified costs the next run five minutes; the same row
presented as settled costs it a wrong decision.

## Scope

`$ARGUMENTS` fixes one of three scopes. The scope decides which **branch file** supplies
the reading and the writing in steps 2, 5 and 6:

| Scope | Target | Writes | Branch |
|---|---|---|---|
| **theme** | the design's global variables, across every mode | `foundations/`, and the inputs in `tokens/` | [`theme-scope.md`](theme-scope.md) |
| **view** | one frame — a screen or a dialog | `components/` for the components on it, plus the delta | [`view-scope.md`](view-scope.md) |
| **component** | one component, wherever it appears | that component's file in `components/` | [`view-scope.md`](view-scope.md) |

**Do the theme scope first, and once.** It decides the font family and base size, which
reflow every screen; per-view spacing settled against the old scale is work thrown away.

## Steps

### 1. Resolve the target and find the spec's home

`$ARGUMENTS` names a scope, or a view or component in whatever words came to hand —
`"Add Expense"`, `LineEditorDialog`, `report detail` — and may include a Figma URL.

Fix the design file first: take `fileKey` and `nodeId` from a URL of the form
`figma.com/design/:fileKey/:name?node-id=1-2`. Absent a URL, check whether the project
records a design file (its agent instructions are the usual home) before asking for one.

Resolve the target on the design side with `get_metadata` on the page to list frame
names; the branch file says how to pick the frame. Name the resolution back to the user,
and ask only when two candidates are genuinely indistinguishable.

**The spec's home** is the directory the project's agent instructions name. Read what is
already there — it is the baseline this run revises, not a blank page. No spec at all means
nothing has been settled yet, which makes every divergence in step 4 open by default; say
so, because it bounds what this survey can conclude.

**Done when** the scope is fixed, one Figma node id is fixed, and the existing spec is
either read or its absence noted.

### 2. Read both ends

Follow the branch file's reading sections: **theme** reads the design's variables through
their bindings; **view** and **component** find what the code already builds, read the
frame, and map every component instance to a Vaadin component.

Everything in this step is fact-finding. Where a branch file marks a row as judgement — an
`override` or `invented` mapping, a value inferred from a drawing — carry it forward as a
question for step 4 rather than settling it here.

**Done when** the branch file's reading criteria are met and every judgement call it
surfaced is on the list for step 4.

### 3. Establish the app's values — from files

Take the **formulas** from the spec's token reference — `round(baseRadius * 2px + 3px,
1px)` and its siblings. They are what the framework derives each scale with, they are
stable across a theme change, and they go stale only on a framework upgrade, which is why
the spec caches them. Then list every property the app's own theme file sets, so step 4
knows what is a deliberate app choice rather than a default.

**Compute the resolved values; do not measure them.** Given the formulas this is
arithmetic, and it is the only correct way round — step 4 is about to change an input, so
the numbers you need are the ones at the **decided** inputs, not the ones the app renders
today. Scored against the outgoing base, the wrong base can look better: a type scale
whose small step is a *clamped* fraction of the medium step lands a design's most-used
size exactly on a token at one base and between two tokens at another.

**Where the spec has no formulas** — a first run, or a framework upgrade since the last
one — say so and stop short of asserting any resolved number or off-scale verdict. Those
rows stay unresolved for a measurement pass to fill; obtaining formulas belongs to whoever
applies the theme and is already in a browser.

**Done when** the formulas are in hand or their absence is recorded, the resolved values
at the decided inputs are computed rather than measured, the version they hold for is
noted, and every property the app's theme sets is listed.

### 4. Interview: turn every judgement into a decision

Build the **design tree** of everything steps 2 and 3 left to judgement, and work it in
rounds per [`interview.md`](interview.md). Its material, across every scope:

- **Divergences** — one per property where the design and the app disagree. Properties
  that already agree need no row and no question.
- **Off-scale values** — solve the formulas backwards and the design's raw pixel values
  disagree with each other: a 9 px field radius asks for one base radius and a 12 px card
  radius for another, at which the field renders 6 px. Each such value sits **between** two
  steps of the scale or **beyond** its end, and each is a global decision with four
  options: correct the design back to the scale, override the derived property globally,
  define one project-level property used everywhere that value appears, or accept the
  nearest token and record the difference. Recommend a property only for a value several
  pixels off *and* recurring; within a pixel or two, the nearest token — a property
  shadowing the scale for one pixel is where drift starts. Where the framework has a real
  property for exactly that value, override it rather than inventing a twin.
- **Judgement calls the branch file surfaced** — mappings, accepted infidelities, domain
  gaps.

Two constraints are **facts to report, never questions**:

- **The accessibility floor.** Contrast, touch-target size and focus visibility are
  constraints. A design value that breaks one is a design defect; the app keeps the
  accessible value, recorded as a settled divergence.
- **A value the design contradicts itself on.** Where a variable and the drawn value
  disagree, the variable wins, and the drawn value is a design defect to report.

**Done when** the frontier is empty and the user has confirmed the tree: every property is
either identical on both sides or carries a decision with a status — **settled** or
**open** — that the user chose.

### 5. Write the spec

Write into the project's spec directory, following any template its own `README` defines
— where it does, that is binding and overrides the branch file. The branch file says which
files and what each covers.

Every decision row carries **settled** or **open**. A divergence resolved in the **app's**
favour is settled, not absent — that row is what stops the next survey reporting the app's
own font as a mismatch. A concern with no divergence still gets its file or its section
saying so; absence recorded is what stops the next survey re-deriving it. A figure only a
browser can produce stays **unverified**.

**Done when** every file the scope calls for exists, every decision row carries a status,
and no measured figure is asserted without a measurement.

### 6. Hand over in the conversation

Write the hand-over as your final message, under the headings the branch file gives. It is
the input to a ticketing pass, so it must stand without this conversation: a reader lifting
it into a ticket should never need to re-read the frame.

Global theming differences do **not** go in a view's delta. They were decided in step 4 and
written in step 5, which is what stops the next survey re-litigating the same radius.

**The survey stops here.** Name the ticketing pass as the next step and leave it to the
user; the delta is theirs to read before it becomes anyone's work.

**Done when** every heading is filled or explicitly marked empty, the spec files are
listed, and the message ends with the next step named.

