How Figma Dev Mode shows real @prepared911/ui snippets, where the templates live, and the check that keeps them honest.
@prepared911/ui component and props. The reference file is the Axon 911 Design System in Figma. A Figma component set maps to a code component only when its properties mirror the code props: same names, same values, same defaults.
Each template sits beside the component it maps and is owned by the same team.
| Unit | Template |
|---|---|
| Component | packages/ui/src/components/<id>/<node-name>.figma.ts |
| Block | packages/ui/src/blocks/<id>/<node-name>.figma.ts |
card.figma.ts, card-footer.figma.ts, menu-sub-trigger.figma.ts). A unit with several nodes has several templates, and each template's component header names the export that node stands for. Template ids (card-footer) are unique across the package.
A template is a parserless figma.code template. It starts with three header lines and ends with a default export that has an id:
// lines after component= say what the snippet cannot express. All are comma-separated names, and the verifier checks them:
| Header | Lists |
|---|---|
// no-code-prop= | Figma properties on the set that have no code prop to map to. A parity gap for the Figma owner. |
// no-figma-property= | Props of the component that a Figma control could stand for (an enum or union of literals, a boolean, a slot, visible copy) with no property on the set. Every such prop is either written by the snippet or listed here. |
// story-only= | Axes drawn only to show a look the browser makes. |
// retired= | Properties still on the set that another property replaced. |
// note= | Free text about a gap or a choice; may repeat. |
no-code-prop name, or a no-figma-property name the component declares as a design prop, is partial. A template with neither is mapped. pnpm --filter @prepared911/ui code-connect:coverage prints both counts and every gap.
A template never reads a property and discards the value, never imports a named glyph, and never writes className or style. A swapped icon for a ReactNode prop goes through getInstanceSwap(...).executeTemplate(); a prop that takes a glyph reference gets a lower-case placeholder (icon={glyph}) and the swap is listed under no-code-prop, because Code Connect cannot name the swapped glyph.
pnpm --filter @prepared911/ui code-connect:check runs in ci:gates and check. It fails when:
scripts/code-connect/no-figma-counterpart.json and is not in scripts/code-connect/unmapped-baseline.json;
source or component does not resolve, a header line is missing, or the url is outside the Design System file;
scripts/code-connect/parts-without-figma-set.json;
Contents, Portal, VisuallyHidden), has no Figma counterpart to map. It goes in scripts/code-connect/no-figma-counterpart.json, one id per entry with a written reason, and never in the baseline.
One rule per kind of axis, so every template reads the same way. Read each Figma property once, at the top of the template, with the getter that matches its Figma type.
| Figma property | Template |
|---|---|
BOOLEAN | getBoolean("Loading") |
VARIANT whose options are False and True (boolean as a variant: Disabled, Icon only) | getEnum("Disabled", { False: false, True: true }). getBoolean is for BOOLEAN properties only; the drift check flags it on a variant. The axis is named for the code prop in sentence case. When there is no such prop, the axis picks a branch of a union (Icon only picks icon plus label instead of children) and the template says so in a constant. |
VARIANT with a third option (Toggle: None, Off, On) | getEnum to lowercase words ("none", "off", "on"), then derive the props from the words. |
VARIANT that is an enum in code (Intent, Variant, Size) | getEnum with one entry per Figma option, mapped to the enum member ("ButtonIntent.Accent"). |
TEXT, SLOT | getString, getSlot. Copy is a copy("...") placeholder, never the Figma text (ADR 0009). |
State, Scene, Story) exist so Figma can draw a look the browser makes by itself. Never invent a prop for one. Apply the first rule that fits:
State=Pressed emits active.// line at the top of the snippet that names the pseudo-class, and no prop. Button State=Hover and State=Focus do this.Scene): do not read it, and list it in the header as // story-only=Scene.// retired=Show label,Pressed. The drift check then stops asking for a mapping and reminds you to remove it from Figma. Anything that a snippet cannot express is written down in the component's table below.
2072:3) has these properties: Intent, Variant, Size, Disabled, State, Icon only, Toggle, Loading, Show leading, Show trailing, Label, Leading and Trailing. Show label and Pressed are retired: Icon only and Toggle replace them.
| Figma | Snippet |
|---|---|
Icon only=True | icon={<Leading />} and label={copy(...)}, no children, no leading or trailing |
Icon only=False | children, plus leading and trailing when Show leading and Show trailing are on |
Toggle=On | pressed and onPressedChange={handlePressedChange} |
Toggle=Off | pressed={false} and onPressedChange={handlePressedChange} |
Toggle=None | no toggle props |
State=Rest | nothing |
State=Pressed | active (data-pressed, no aria-pressed), except on a toggle, where active is not defined |
State=Hover | a comment: the browser paints :hover, Button has no prop that forces it |
State=Focus | a comment: the browser paints :focus-visible, Button has no prop that forces it |
Disabled=True | disabled |
Loading on | loading and loadingLabel={copy(...)}, except on a toggle, where loading is not defined |
tooltip, type, pressedIcon, defaultPressed, preventFocusOnPress, className and native attributes have no Figma property. Hover and Focus cannot be set in code at all. handlePressedChange and the copy keys are placeholders the developer fills in.
code-connect:check reads only the repository. code-connect:verify also reads scripts/code-connect/figma-sets.snapshot.json, a checked-in read of every component set and standalone component in the Design System file (property names, types, defaults and options, without the icon lists). It runs in the unit tests (tests/code-connect-templates.test.ts):
url, renders the snippet for every combination of the set's variant and boolean properties (a seeded sample when the product is above 600), and fails when:
getX reads a property the set does not have, with the wrong getter, or a getEnum map skips or invents an option;className or style, writes a prop its component does not declare, or names an enum member that does not exist;no-figma-property;--types, a rendered snippet does not type-check against the real components. copy, lower-case placeholders and <Name>Icon stand-ins are the only names a snippet may use without importing them.code-connect:check reads only the repository, so it cannot see Figma change under a template. code-connect:drift closes that gap:
getBoolean on a variant);getEnum map that skips an option the set gained, or names one it lost;// story-only=, // retired= or // no-code-prop=.code-connect-drift workflow runs it every night and on demand with a Figma token read from the FIGMA_ACCESS_TOKEN repository secret, or the Figma repository secret when that one is empty, and a failed run is the alert. When neither secret is set the run skips with a warning that names both. It is not a pull request check and not a required check.
publish --dry-run does need one: it lists the files it would publish, then exits 1 with "Couldn't find a Figma access token". Publishing is a separate, manual step; see the Figma Code Connect decision record in packages/ui/docs/decisions/0030-figma-code-connect.md.
pnpm --filter @prepared911/ui code-connect:coverage prints, for every exported component, whether it is mapped, partial (a template that lists gaps), without a Figma counterpart (exempt with a reason) or unmapped, and lists the gaps of each partial one. A part that Figma draws inside its parent's set (CardContent as the Card Content slot) is exempt in parts-without-figma-set.json; a whole unit with no Figma component (value-list-field) is exempt in no-figma-counterpart.json.