Building Design to HTML: a Figma plugin that exports frames to HTML and CSS
How I built Design to HTML, a Figma plugin that turns a selected frame into clean HTML and CSS, and the per-side border bug that taught me to read Figma's stroke model properly.
I kept doing the same boring thing: open a Figma frame, then rebuild it in HTML by hand, matching padding, colors, and fonts by eye. So I built a plugin that does the first pass for me. Select a frame, click export, and get a single HTML file that already has the layout, colors, and fonts.
What it reads
A Figma plugin runs in two halves. code.ts runs in the sandbox with the figma API and can read the document. ui.html is a small iframe for the interface. They talk over postMessage.
The core is a walk over the selected node tree. For each node I decide how it should behave in CSS:
- Auto-layout frames become flexbox. Direction, gap, padding, and alignment map almost one to one. Figma's hug, fill, and fixed sizing become
fit-content,flex: 1, and a pixel width. - Frames and groups without auto-layout become a relative box with absolutely positioned children, placed by each child's x and y.
- Text keeps its content, font family, weight, size, color, alignment, line height, and letter spacing.
- Vectors and icons are inlined as SVG, so they stay crisp. Image fills export as PNG.
Fonts are the nice touch. I collect every font a text node uses, then emit one Google Fonts link with just those families and weights. Open the file in a browser and it looks right, with no manual font setup.
The border that gave me away
The first export that looked wrong was a table. In Figma each row had a line under it, nothing else. My export drew a full box around every row.
The cause was my stroke code. I read strokeWeight and wrote border: 1px solid, which paints all four sides. But Figma lets you set a stroke per side. When the sides differ, strokeWeight comes back as figma.mixed, and I was falling back to a uniform border.
The fix was to read the four side weights, strokeTopWeight, strokeRightWeight, strokeBottomWeight, and strokeLeftWeight, and emit border-bottom, border-top, and so on for only the sides that carry a weight. I keep the border shorthand when all four match. After that, a bottom-only rule exports as a single line, the way it was drawn.
The lesson is the usual one. The API had the full model the whole time. I just assumed a simpler one.
Three ways out
The preview started as a plain textarea. It now shows syntax-highlighted code, using a small offline highlighter I wrote for HTML and CSS so the plugin needs no network for it. Tags and selectors are one color, attributes and properties another, strings and values a third.
There are three export modes:
- HTML and CSS in one self-contained file
- HTML on its own, linking to
styles.css - CSS on its own
Copy or download acts on whichever mode is active, and the download picks .html or .css to match.
What I checked
The risky part is the tree walk and the value converters, so I left a small harness behind. It stubs the figma global, feeds a sample frame through the compiled plugin, and asserts the output: the Google Fonts link, the flex mapping, the border rules, and the three export variants. It runs in plain Node, with no framework. When the border fix landed, one new assertion proved a bottom-only stroke stays a single line.
Where it is now
The plugin is submitted to the Figma Community and under review. The code is on GitHub, and it is free to use. If it saves you some time, there is a Ko-fi link in the plugin and the readme.
Building scalable systems and developer-first tools. Lead Software Engineer at DSRPT.
Frequently asked
-
It is a Figma plugin that exports a selected frame to a single, self-contained HTML file, keeping the layout, colors, per-side borders, radius, and typography, and adding a Google Fonts link for the fonts you used.
-
The plugin read strokeWeight, which paints all four sides. Figma allows a stroke per side, and when sides differ strokeWeight is figma.mixed. Reading the four per-side weights and emitting border-top, border-right, border-bottom, and border-left fixed it.
-
Yes. There are three modes: HTML and CSS together, HTML that links to styles.css, or CSS on its own. Copy and download act on the active mode.