Structure is right, but the visuals look off? Closing the last few pixels between Adobe XD and Figma!
The very last thing that lingers during an XD-to-Figma migration is that nagging feeling of “the structure is right, but somehow the visuals look off.” 🤔 (For the “structural” side of conversion — Auto Layout and Components — see “Skip the XD-stack rebuild — drop your layouts straight into Figma Auto Layout!” and “Faithfully recreate your XD components — drop them straight into Figma!” for the details!)
Gradient angles drift a little. Text gets clipped at a frame’s edge. Text inside an Auto Layout sits a few pixels lower than it should — these are rooted in fundamental rendering differences between XD and Figma, and no amount of structural copying will address them.
Most converters stop here and say “the structure came over, so we’re done.” But for anyone actually doing the migration, the post-conversion cleanup is where most of the time goes 😇
Pixel Fine Converter takes on this “last few pixels” problem, one case at a time! We can’t promise a perfect recreation, but we’ve built in four fine-tuning passes specifically designed — with XD and Figma’s rendering differences in mind — to cut down the amount of post-conversion cleanup you have to do 🔧
Note: Fine-tuning is a Pro plan ($29, one-time purchase) feature. The plugin itself is free to install, and the Free plan still handles structure, styles, groups, and other basic conversions.
🧩 Why fine-tuning is needed
XD and Figma are both vector-based design tools, but their internal rendering logic differs significantly.
| Area | XD | Figma |
|---|---|---|
| Gradient coords | Normalized (0–1) space, independent of element size | Bounding-box space — aspect ratio affects the angle |
| Font metrics | Its own text-layout engine | Can occupy roughly 1.15–1.45× the space for the same font |
| Single-line lineHeight | Ignored when rendering (no visible effect) | Always applied (can cause clipping if too small) |
| Half-leading | Baked directly into the single-line text’s Y | (lineHeight - natural height) / 2 distributed evenly above and below the glyph |
Structure conversion alone can’t fix any of this. Pixel Fine Converter’s fine-tuning is the dedicated step that applies a counter-measure to each of these differences so you have less cleanup afterwards! We can’t perfectly correct everything, but we apply what we can, knowing why the mismatch happens in the first place.
Below we dig into each of the four features — what the problem is, why it happens, and how we resolve it!
🎨 Gradient angle correction
Problem
XD defines gradients in a normalized (0–1) coordinate space, so the angle stays the same regardless of element size. Figma uses bounding-box space, so on non-square elements, diagonal gradient angles drift.
This becomes very obvious on elements with extreme aspect ratios. On a 960×50px header bar with a 45° gradient, XD renders the intended diagonal, but Figma ends up drawing something much closer to horizontal — a visible mismatch.
Why the drift happens
XD computes the gradient direction in pixel space. “Top-left to bottom-right” is the same angle whether your element is square or wide. Figma samples in a normalized [0,1]² space; on a wide element the horizontal axis is “compressed,” so a 45° spec actually renders at a much more horizontal angle.
So the same (0,0) → (1,1) spec means “a diagonal line in pixel space” in XD but “a diagonal line in normalized space” in Figma — and on non-square elements those aren’t the same angle.
Solution
Pixel Fine Converter recomputes the affine transform matrix with the element’s aspect ratio (W/H) factored in. We re-project XD’s normalized coordinates into Figma’s pixel space to correct the angle!
Concretely: we expand the gradient’s start/end points into pixel space and build a transform weighted by W² and H². The result is adjusted so that, when sampled by Figma, the angle matches what XD drew. Unglamorous, but effective 💪
Note that this correction applies only to linear gradients. Radial gradients use a different conversion path (composing and inverting transform matrices).
Auto-skip
The correction is automatically skipped in these cases:
- ✅ Square elements — if width and height differ by 0.01px or less, the two spaces are effectively identical
- ✅ Horizontal / vertical gradients — angles that aren’t affected by aspect ratio
Trade-off
The correction is ON by default, but in rare cases it can make things worse. If the post-conversion gradient looks off, you can switch it OFF in the options to revert to the naive behavior.
✂️ Preventing text clipping
Problem
XD and Figma use different font metrics. For the same font and size, Figma can occupy roughly 1.15–1.45× the space. Text that fit neatly inside a frame in XD can get clipped on the right or bottom edge in Figma.
This is especially noticeable inside clipping frames (clipsContent=true). The overflow is only 1–3 pixels — but in a clipping frame those few pixels show up as visible cut-off text. A few pixels may not sound like much, but you notice them 😅
Why the rendered size differs
Font files (TTF/OTF) store multiple sets of metrics that determine line height — most prominently the sTypo (OS/2 table) and hhea (hhea table) systems.
XD and Figma read different metric systems, so the same font file produces different line heights in each. The size of the difference varies by font, but it’s especially large for CJK fonts (Yu Gothic, Noto Sans JP, etc.): while Latin fonts are around 1.15–1.25× larger, CJK fonts can be around 1.45×. That’s why Japanese-heavy designs tend to surface the issue first!
Solution
When this option is ON, we compute the actual dimensions of every text node inside clipping frames, and when overflow is detected, we extend the frame by just enough pixels.
- 🔍 Detect: check whether each text node’s right/bottom edge exceeds the frame
- 📐 Measure: find the maximum overflow across children and round up to the pixel
- 📦 Extend: expand frame and mask shape together — works whether the mask is rectangular, elliptical, or a vector
Rather than applying a fixed padding, we compute overflow per child so no wasted empty space is introduced 🎯
Scope and exclusions
This feature targets text nodes with textAutoResize = WIDTH_AND_HEIGHT (point text). Text box–type (fixed-width) text wraps to the box width, so it’s out of scope. Rotated clipping frames are skipped because we can’t guarantee overflow detection accuracy.
Trade-off
Frame sizes end up slightly larger than the original XD dimensions. If you need pixel-perfect layout, you may still need to tweak by hand.
📏 Line-height normalization
Problem
In XD, if lineHeight is smaller than fontSize, single-line text is still displayed in full — XD doesn’t use this value when rendering single-line text.
Figma always applies lineHeight to the text box’s height. If lineHeight < fontSize, the text box gets shorter than the glyph and the top and bottom get clipped. A 72px heading with a 24px lineHeight ends up mostly cut off in Figma 💦
Why lineHeight < fontSize happens in the first place
You might wonder who sets such a weird value in XD. It commonly happens when a designer sets lineHeight for multi-line text and later changes the text to a single line. Because XD doesn’t visibly apply that value, the old number just stays in the file — and no one notices. Classic XD!
Solution
When this option is ON, we clamp lineHeight to be at least the natural line height that Figma actually measures (naturalLineHeight). Rather than just comparing to fontSize, we load the font in Figma, measure the real rendered height, and use that as the floor.
This way the correction reflects per-font metrics accurately — roughly fontSize × 1.45 for CJK fonts, roughly fontSize × 1.2 for Latin fonts.
⚡ Solving the half-leading problem
Problem
This is the trickiest of the fine-tuning features!
XD’s single-line text ignores lineHeight for positioning. Figma, on the other hand, distributes (lineHeight - natural height) / 2 evenly above and below the glyph (half-leading).
The difference is especially visible inside Auto Layout frames. Imagine a button with a 24px heading and a 14px sub-label side by side. In XD they sit neatly aligned to the top; in Figma each one gets its own half-leading on top, and baselines end up a few pixels off from each other.
Coordinate contamination — making it worse
To make matters worse, XD’s coordinate data itself can already be “contaminated” by lineHeight. In textAutoResize=WIDTH_AND_HEIGHT mode, XD sometimes bakes a lineHeight offset into the Y coordinate of the text.
Since lineHeight has no visible effect in XD, designers don’t notice. But when the conversion uses those coordinates as-is and then Figma adds half-leading on top, you get two layers of drift stacked on top of each other (“XD coord drift + Figma half-leading drift”). The complexity is impressive, in the worst possible way 😵💫
Solution
Pixel Fine Converter applies different normalization strategies depending on the Auto Layout direction:
| Direction | Action | Why |
|---|---|---|
| Horizontal Auto Layout | Reset lineHeight to AUTO | Side-by-side text cares about glyph centering; AUTO produces a natural visual alignment |
| Vertical Auto Layout | Back-compute an appropriate height from XD’s child coordinate deltas | When the coordinate data is trustworthy, we can restore XD’s intended spacing |
Vertical Auto Layout: detecting coordinate contamination
For the vertical case, Pixel Fine Converter back-computes “the height XD intended” from the Y-coordinate deltas between child nodes. We take the ratio between the computed XD height and the text’s actual Figma height, and use that ratio to judge how trustworthy the coordinates are:
- 📊 Ratio ≤ 1.5 → Coordinates are trustworthy. Use the XD-derived height as
lineHeight - 📊 Ratio > 1.5 → Coordinates are likely contaminated by lineHeight. Fall back to
lineHeight=AUTO
Why 1.5? Normal font-metric differences between XD and Figma fall in the 1.0–1.3× range. A ratio greater than 1.5 can’t be explained by metrics alone, which strongly suggests the coordinate itself was influenced by lineHeight.
It doesn’t work perfectly in every case, but it reduces half-leading drift noticeably for most real-world layouts 🛡️
Controlling the scope
Half-leading normalization has three modes to match how aggressive you want to be:
- 🔴 OFF — No normalization (default)
- 🟡 Auto-layout children only — Normalize only text inside Auto Layout frames (recommended)
- 🟢 All — Normalize every eligible text node (if you want to be aggressive about it)
The reason “Auto-layout children only” is the default is that inside regular frames, lineHeight is sometimes used deliberately for vertical centering — for example button text centered vertically via lineHeight. Normalizing would break that.
Trade-off
Normalized text loses its original lineHeight value. If you later switch to multi-line text, the line spacing will use Figma’s default rather than XD’s original value.
⚠️ Limits and scope (being upfront about it)
We’ve walked through the four fine-tuning passes in detail, but they’re all mitigations for a fundamental problem: XD and Figma render differently. They won’t work perfectly in every case, so here are the trade-offs in plain language:
- Gradient angle correction can rarely make things worse. If the post-conversion gradient looks off, turn it OFF and reconvert
- Text clipping prevention changes frame sizes, which is a trade-off against pixel-perfect layouts
- Line-height normalization overwrites the original
lineHeight. If you later change the text to multi-line, you’ll want to reset the spacing - Half-leading normalization defaults to only Auto Layout children. You can apply it to regular frames too by turning “Auto-layout children only” off, but expect more unintended changes
- Overflow detection on rotated clipping frames is skipped
- The full option behavior is documented in Guide: Fine-tuning option reference
You can’t fully eliminate rendering-engine differences at the level of principle, but “if the plugin can absorb 80–90% of the irritating drift, the remaining 10–20% of cleanup is a lot lighter” — that’s the philosophy we develop to. We’ll keep working to make the plugin one you can genuinely rely on.
🚀 Try the Free plan first to check conversion quality
Pixel Fine Converter is free to install from the Figma Community! The Free plan includes the full set of basic conversions — shapes, text, styles, groups, masks, images, transforms — so you can check the conversion quality on your own .xd file right away.
For fine-tuning and the other detail-oriented conversion features, you can unlock them with the Pro plan ($29, one-time purchase — no subscriptions) 🎁
One-click install from Figma Community
Related pages
- Guide: Fine-tuning option reference — default values and details for each option
- Features: Auto Layout conversion — converting XD stacks to Figma Auto Layout
- Features: Components conversion — recreating component and instance structure
- Features: Prototype & States — prototype connections and state conversion
- Blog: XD→Figma migration practical guide — Background, process, pitfalls, and tool selection