❯ Developer Tools · Design · 2025
Interlace
A Figma plugin that turns CSS into native Figma nodes, with resolvers for px/rem/vw units, colors, gradients, flexbox, shadows, and typography.
The problem
Designers hand code to developers and get pixels back, then the round-trip starts: screenshots, comment threads, "can you make the padding match?" Interlace closes part of that gap inside Figma by importing CSS and rendering it as editable native nodes instead of flattened images.
The approach
A TypeScript Figma plugin that parses CSS and resolves styles into Figma's
object model: units (px, rem, vw), colors, gradients, flexbox layout,
box-shadow and filter blur, and typography. Element creators map <img>,
<video>, <button>, and <input> variants (text, checkbox, radio, range,
color) to native nodes, with plugin-data persistence and static-image
fallbacks where a live element cannot exist.
Architecture
- CSS Source
- Style Resolvers
- Node Creators
- Native Figma Nodes
- Plugin Runtime
FLOWS
- CSS SourceparseStyle Resolvers
- Style ResolversstylesNode Creators
- Node CreatorsrenderNative Figma Nodes
- Plugin Runtimeinput CSSCSS Source
The plugin runs inside the Figma editor. A resolver layer owns the CSS
semantics, a creator layer owns Figma node construction, and the two meet at a
typed boundary. The build pipeline is esbuild with React JSX widgets in watch
mode, ESLint, and tsc type-checking, so the plugin stays a plain TypeScript
package with no heavy framework. A companion Next.js app with Tailwind and
Zustand hosts the editing experience and gives the design system a place to
live outside the plugin.
Key decisions
Typed boundary between CSS and Figma
Resolvers return a shape the creators can construct without reinterpreting CSS.
Native nodes over images
Everything lands editable, which is the whole point of importing into Figma.
Static fallbacks for live elements
A checkbox becomes the best native approximation rather than a blank.
esbuild over a plugin framework
Fast watch builds and no runtime bloat inside the editor.
What I'd do differently
The web app and the plugin started in one repo but never shared much code, and the shared abstractions were the resolver shapes. I would extract the resolver layer into a standalone package first and let both sides consume it.