You spend an afternoon tightening a comp in Figma, hand it to Rork, open the build on a device, and think: everything is here, and it's still wrong.
That was my situation. Every element present. Colors correct. Order correct. Put it next to the Figma frame and it read as a different screen. The frustrating part wasn't the mismatch — it was that I couldn't name it. "Looks a bit clumsy" was as far as I got, which meant every Fix Now request came out vague, and every fix came back slightly off target. As an indie developer, design and implementation are the same pair of hands, so that vagueness comes back as hours you can't account for.
What broke the loop was giving up on looking. I opened Figma's Dev Mode, wrote down the values for one component, opened the generated StyleSheet beside it, and compared numbers. Tedious work, but the shape of the problem appeared quickly. The differences weren't scattered. They kept landing on the same five properties.
Below are those five, plus the Auto Layout translation table I keep on hand and the token file I now attach to every prompt.
Compare numbers, not screenshots
One process note before the list.
Putting the Figma frame and a device screenshot side by side did not work well for me. Eyes judge the overall impression, which is exactly the wrong resolution for finding a root cause. More than once I was certain the padding was too generous when the actual culprit was line height.
The approach that worked: pick one component, write down the Figma values, and read the corresponding values out of the generated code. Dev Mode gives you CSS-equivalent numbers directly if your plan includes it; if not, copying the right-panel values by hand is enough.
Do this for five or six components and the same category of difference shows up again and again. At that point you stop fixing instances and start fixing categories — diagnose the cause once, and every later component falls to the same prescription.
Translate Auto Layout into flexbox vocabulary first
Auto Layout and flexbox think alike, which tempts you to treat them as interchangeable. The names differ, though, and that difference is exactly where prompt instructions get slippery.
| Figma (Auto Layout) | React Native style | Where it bites |
|---|---|---|
| Vertical layout | flexDirection: 'column' | RN defaults to column — the opposite of the web |
| Horizontal layout | flexDirection: 'row' | — |
| Gap between items | gap | Substituting margin doubles the edge spacing |
| Padding (per side) | paddingTop, etc. | Figma's grouped vertical/horizontal fields mislead here |
| Main-axis alignment | justifyContent | — |
| Cross-axis alignment | alignItems | — |
| Fill container (main axis) | flex: 1 | The single most common mismatch — see below |
| Fill container (cross axis) | alignSelf: 'stretch' | Same. width: '100%' fights the parent's padding |
| Hug contents | No size specified | — |
| Fixed | width / height | — |
| Wrap | flexWrap: 'wrap' | — |
gap is available from React Native 0.71 onward. On older setups you fall back to margins, which means something has to cancel the trailing child's spacing — and that cancellation is a reliable source of generated-code drift. Check your version before you start.
The five places the gap collects
1. Fill and Hug swapped
This was the most frequent by a wide margin. Something set to Fill container in Figma comes back sized to its contents, or the reverse.
It reads as "vaguely centered" or "there's leftover space on the right," which is why it rarely gets suspected as the cause.
When fixing it, separate main axis from cross axis. Inside a column container, "stretch to full width" is a cross-axis concern, so alignSelf: 'stretch'. "Absorb the remaining height" is main axis, so flex: 1. Reaching for width: '100%' instead adds to the parent's padding and pushes the element past the edge.
2. Line height
Figma text is frequently left on Auto line spacing, in which case the real value comes from the font's metrics. React Native's lineHeight is an explicit number, and if you omit it you inherit a platform default. The two diverge easily.
A 2–3px difference across a five-line paragraph is a 10px difference in block height. Nearly every time I thought "the spacing is too loose," this was it.
If Figma specifies line spacing as a percentage, convert with fontSize × percentage. If it's on Auto, read the computed value in Dev Mode and pin that number in code. Better still, switch the design side to explicit values so the next comparison is trivial.
3. Letter spacing
Less visible than line height, but it matters in headings. Figma expresses letter spacing as a percentage; React Native expects points. Convert with fontSize × percentage ÷ 100.
Two percent on a 12px font is 0.24px. That sounds negligible, but it accumulates across a heading — enough to move a line break by a character. One character is enough to change how a screen reads.
4. Stroke alignment
Figma strokes can be Inside, Center, or Outside. React Native's borderWidth is always inside.
A 100px box with a 1px Outside stroke occupies 102px in Figma and 100px in React Native, with the border eating 1px of the interior. When cards sit in a row, this is why the spacing between them looks subtly uneven.
The low-drama fix is to standardize on Inside strokes in the design file. If Outside has to stay, the code needs the box dimensions increased by twice the stroke width.
5. Shadows
This is the one where you should stop chasing an exact match.
A Figma drop shadow carries X, Y, blur, spread, and a color with opacity. Classic React Native shadows give iOS shadowColor, shadowOffset, shadowRadius, and shadowOpacity, while Android gets elevation alone — no color, no direction. There is also no classic equivalent for spread.
Blur doesn't transfer directly either. As a starting point, set shadowRadius to roughly half the Figma blur and tune on a device. Trying to derive an exact formula took longer than eyeballing from a close starting value.
Newer React Native versions support boxShadow, but availability depends on your version and architecture settings. Confirm it works in your project before designing around it.
Attach the numbers as text, not just a screenshot
All five have something in common: none of them are readable from a screenshot.
Images communicate structure and layout well. They cannot tell you whether a line height is 22 or 24. So Rork fills that in by inference, and the inference misses. Obvious in hindsight.
Now I send numbers alongside the image. Exporting Figma Variables directly would be ideal, but the Variables read API is limited to higher-tier plans. Without it, writing out only the values you actually use is plenty — about ten minutes of work.
// tokens.ts — only the values actually in use, lifted from Figma Variables
// as const keeps fontWeight from widening to string so StyleSheet accepts it directly
export const t = {
color: {
surface: '#171B22',
border: '#232A34',
text: '#E6E9EF',
textMuted: '#9AA4B2',
accent: '#4F8CFF',
},
space: { xs: 4, sm: 8, md: 12, lg: 16, xl: 24 },
radius: { sm: 6, md: 12, pill: 999 },
type: {
// Pinning lineHeight and letterSpacing in points is the whole point of this file
title: { fontSize: 20, lineHeight: 26, letterSpacing: -0.2, fontWeight: '600' },
body: { fontSize: 15, lineHeight: 22, letterSpacing: 0, fontWeight: '400' },
caption: { fontSize: 12, lineHeight: 16, letterSpacing: 0.2, fontWeight: '400' },
},
} as const;Paste that into the prompt with one instruction: reference these tokens for color, spacing, and typography, and don't write raw values into components. That alone cut down the stream of hardcoded #171B22 in generated output considerably.
Once the code comes back, leaving the Figma spec in comments makes the next comparison much faster.
import { Platform, StyleSheet } from 'react-native';
import { t } from './tokens';
export const card = StyleSheet.create({
root: {
// Figma: Vertical / gap 12 / padding 16 / Fill container horizontally
flexDirection: 'column',
gap: t.space.md,
padding: t.space.lg,
alignSelf: 'stretch', // width: '100%' would fight the parent's padding
backgroundColor: t.color.surface,
borderRadius: t.radius.md,
// Figma: Stroke 1px / Inside (Outside would make the outer box 2px larger)
borderWidth: 1,
borderColor: t.color.border,
// Figma: Drop shadow Y=4 / blur 16 / spread 0 / #000 24%
...Platform.select({
ios: {
shadowColor: '#000',
shadowOffset: { width: 0, height: 4 },
shadowRadius: 8, // start at half the blur, then tune on device
shadowOpacity: 0.24,
},
// Android only has elevation — no color, no direction. Approximate and move on.
android: { elevation: 4 },
}),
},
title: { ...t.type.title, color: t.color.text },
body: { ...t.type.body, color: t.color.textMuted },
});The broader idea of treating tokens as a single source of truth gets fuller treatment in stopping color and spacing drift across regenerations with design tokens. If you'd rather express the same vocabulary in Tailwind, setting up NativeWind in a Rork app covers that path.
Fix in a fixed order
When several differences surface at once, fixing whatever catches your eye creates rework, because changing an upstream property moves everything downstream.
The order I settled on:
- Structure — nesting and
flexDirection. Get this wrong and every later adjustment is thrown away - Dimensions — Fill/Hug,
gap,padding. One and two account for most of the visual gap - Type —
lineHeight,letterSpacing,fontWeight - Decoration — corner radius, borders, shadows
Do three and four first and any layout shift from step two forces you to redo them. Lock down one and two, and the rest can be tuned independently.
The same granularity works for Fix Now requests. Not "make it match the design," but "set this component's gap to 12 and padding to 16" — name the layer and the number.
Deciding what you won't match
Matching Figma and a real device exactly isn't a realistic goal, and I've stopped treating it as one.
Shadows will only ever approximate on Android. Font rendering differs between Figma and each platform. And if you honor the user's Dynamic Type setting — which you should — the text will not be the comp's size anyway. It's also something App Store reviewers look at, and pinning it would make the app worse, not better.
So I decide up front what's in scope. Structure, dimensions, and typographic hierarchy get matched. Shadow minutiae and anything driven by user settings do not. Agreeing on that line with the design side ahead of time keeps review from turning into an argument about a single pixel.
Generation tools like Rork genuinely reduce implementation time. What I also noticed is that some of the reclaimed time moved straight into closing the gap with the design. Getting through that work quickly means talking in numbers rather than impressions.
What to try next
Pick your most reused component — a card, a list row, whatever appears most often.
Put its Figma values next to the generated StyleSheet and check only the five properties above: Fill/Hug, line height, letter spacing, stroke alignment, shadow. Two or three will almost certainly apply. Write those values into tokens.ts and attach it to your next prompt. That's one full pass.
After the first pass, later components fall to the same prescription. By my third component, the side-by-side comparison had become unnecessary.
If this shortens the detour for someone stuck at the same spot, that's a good outcome.