How to Import a React or Vue Site into Figma

See how a CLI capture turns a live React or Vue app into editable, auto layout frames in Figma.

TUTORIALDEVELOPERPublished

A React or Vue site looks like a finished page in the browser, but the HTML its server actually sends is close to empty. Import it the same way you'd import a static page and you'll capture that empty shell, not the page a visitor sees. This is where the UnHTML CLI comes in: it runs the app first, waits for the JavaScript to finish building the page, and captures the result. This walkthrough covers why the shell problem happens, how the CLI solves it step by step, and what lands in Figma once the capture is done.

Why React and Vue Sites Need a Different Capture Path

Most React and Vue apps use client-side rendering. The server's job is small: it sends a minimal HTML document and a JavaScript bundle, then gets out of the way. As MDN's client-side rendering glossary entry puts it, a pure client-rendered response is little more than an empty root container and a single script reference. The actual content gets built afterward, in the browser, through DOM manipulation. All the logic, data fetching, templating, and routing happens on the client, as web.dev's overview of rendering on the web explains.

You can see this yourself: disable JavaScript on a client-rendered app and reload it. The page goes largely inert, because there was never much in the initial response to begin with. That's exactly the problem for any capture tool that only reads what the server sent. It gets the shell, and the shell tells it almost nothing about layout, spacing, or content.

For a capture tool, this means a React or Vue app needs its JavaScript executed before capture, not instead of it. Read the raw source and there's nothing worth importing. Let the app render first and there's a full page of frames, text, and images waiting to come into Figma.

This is different from importing a marketing site built with plain HTML and CSS, where the server response already contains the finished page. A capture tool aimed only at server HTML works fine there, and fails quietly on a React or Vue app, returning a page of near-empty divs instead of an error. That silent failure is the real risk: you don't find out the capture was wrong until you open Figma and see nothing worth designing with.

How the UnHTML CLI Captures a Live URL

The CLI exists for exactly this case, and it's one command. No project setup required on your side:

  1. Install and run the capture command against the live URL.
    node capture/cli/capture.js https://your-app.example.com --out scene.json
    
    This points the CLI at the page you want, with an output path for the result.
  2. It opens a headless browser against that URL. It parses the HTML, then executes the page's scripts, the same fundamental technique behind Chrome's own headless dump-dom flag:
    chrome --headless --dump-dom https://example.com
    
    Chrome for Developers documents this as parsing HTML into a DOM, running any scripts that alter it, then serializing the result back to HTML. The CLI applies that idea to a full capture pipeline rather than a single dump.
  3. It waits through the page's behavior. Full-page scrolling runs so content loaded on scroll gets a chance to mount, and remote assets are retrieved without hitting CORS limitations that would block a capture from inside the browser itself.
  4. It writes the result to scene.json. That file holds the fully rendered DOM state, structure, styles, and asset references, ready for the plugin to read.
  5. Import scene.json from the Figma plugin. Because the capture already happened on the command line, the plugin doesn't need live network access at import time. You're importing a file, not re-fetching a page.

For a small marketing page, this whole sequence takes seconds. For a large Vue dashboard or a React app with many routes, it's still the same five steps, just with more to scroll through and more assets to fetch.

A few things are worth checking before you run the command. Make sure the URL you're capturing is the one that actually renders your app, not a login gate or a loading screen sitting in front of it. The CLI captures whatever DOM state exists when it stops waiting, so a page still mid-fetch produces an incomplete scene. If your app relies on authentication, capture a route that's reachable without it, or a state you can reach directly. And because remote assets get retrieved during capture, a slow image host or font CDN can add real time to the run on a content-heavy page.

From CLI Capture to Figma Layers

Once scene.json is in Figma, the plugin turns the captured DOM into native layers: frames, text, images, and vectors, mapped from the page's own CSS.

Auto layout is where most of that mapping shows up. Figma's direction, gap, padding, and resizing controls track closely to CSS flexbox, as Figma's own guide to auto layout lays out: gap distributes objects the way justify-content and CSS gap do, padding mirrors CSS padding, and the hug contents and fill container resize modes correspond to content-driven sizing and flex: 1. Want the property-by-property breakdown? See how CSS flexbox maps to Figma auto layout.

Fonts get the same honest treatment. A font that isn't installed locally gets substituted, Inter by default, and every substitution is recorded in the import report rather than silently swapped. The mechanics of that substitution and logging are covered in Figma font substitution.

Not everything maps cleanly, though. A complex CSS grid, or a layout built on positioning tricks rather than flex, can fall back to absolute positioning instead of auto layout. This is not a pixel-exact clone of the original page, and the import report says so directly rather than hiding the gap.

CLI Capture vs. the In-Plugin Re-Render

The plugin can also re-render a page with scripts turned on, inside a sandboxed frame, directly at import time. That path exists, but it's opt-in, because it needs live network access at the moment you run the import.

For a heavy React or Vue application, capturing first with the CLI and then importing scene.json is the more reliable route. The rendering work happens once, on the command line, with time to wait out scrolling and asset loading. The in-plugin re-render is convenient for a quick check on a lighter page, but for anything with real client-side complexity, the two-step CLI workflow is what holds up.

There's also a workflow reason to prefer the CLI beyond reliability. A scene.json file is portable: capture a route once, hand the file to a teammate, or re-import it later without hitting the live URL again. The in-plugin re-render has to reach the network every time, which means every re-import depends on the site staying reachable and unchanged in the meantime.

Layer Caps and Pricing for Larger Captures

A React or Vue app can easily produce more layers than a static page, so it's worth knowing the caps before you capture a large one. As of 2026-09-21, the free tier already applies auto layout conversion, with a limit of 500 layers per import. Pro, a one-time $39 charge per editor, removes that layer cap and adds components and text styles. Studio, a one-time $119 charge, licenses Pro for up to five editors on a team. All tiers include a year of updates. Check unhtml.pro directly before you buy, since pricing can change.

Conclusion

The reason a React or Vue site needs the CLI comes down to one fact: the server barely sends anything, and the real page only exists after JavaScript runs. The CLI handles that by capturing the live, executed DOM into scene.json, so the Figma plugin has a complete page to import instead of an empty shell. From there, the workflow is the same as any other capture: auto layout mapped from CSS, fonts substituted and logged, and an import report that tells you exactly where the conversion held up and where it didn't.

Questions

Why can't a React or Vue site be imported the same way as a static HTML page?
A React or Vue site typically ships a near-empty HTML shell and builds the real page afterward with JavaScript. Importing the raw server response captures the shell, not the page. The CLI runs a headless browser against the live URL, waits for the JavaScript to finish building the DOM, and captures that result instead.
Do I need to write any code to use the CLI?
No. It is a single command: node capture/cli/capture.js followed by the URL and an out path for the scene.json file. No project setup or framework-specific configuration is required on your side.
When should I use the CLI instead of the plugin's built-in re-render?
The plugin can re-render a page with scripts on inside a sandboxed frame, but that path is opt-in and depends on live network access at import time. For a heavy React or Vue application, capturing first with the CLI and importing the resulting scene.json is the more reliable route.
Does capturing a React or Vue site with the CLI send my page content anywhere?
No. There are no network requests by default. The CLI runs locally against the URL you give it, and remote asset fetching and in-plugin script execution are both off unless you turn them on.
Will the imported layers use Figma auto layout or absolute positioning?
As of 2026-09-21, the free tier already applies auto layout conversion where the page's CSS maps cleanly, with a 500-layer cap per import. Layouts that don't map cleanly fall back to absolute positioning, and the import report notes where that happened.