* feat(code): link code to canvas layers and underline design issues
Code in the Code tab and layers on the canvas were unrelated: finding the
element behind a layer, or the layer behind an element, meant reading names.
Generated Design JSX and Tailwind JSX report the layer behind each element
in the order elements open, and edited Design JSX keeps the source line of
every element through the sandbox and renderer, so hovering an element
highlights its layer, Cmd/Ctrl-click brings it into view without changing
the selection the code shows, and errors and warnings from the design check
are underlined on the property that causes them.
* feat(code): explain the Code tab when nothing is selected
With no selection the editor showed a starter frame that read like a real
layer. The tab now says it shows the selected layers' code and offers Write
JSX, which opens the editor focused on the starter template.
* refactor(code): group code-to-layer linking into its own domain
Layer link types, issue mapping, and the hover and reveal behavior move
from the Code panel and a component file into src/app/code/layers, with
useCodeLayers as the panel's entry point, so app code no longer imports
types from components.
* fix(code): underline off-scale gaps after the spacing rule renamed its property
* feat(code): mark the layer of the element around the cursor
Hover highlighting and ⌘-click reveal replaced by one model: the element
around the cursor marks its opening and closing tag names and outlines its
layer on the canvas while the editor has focus. ⌘-click also collided
with CodeMirror's add-a-cursor gesture. Read-only Tailwind JSX now takes a
cursor so it links the same way.
Leaving the editor now ends a live Design JSX edit as one undo step.
Before, canvas edits made after typing never reached the code until the
tab was reopened, and their undo entries landed before the edit's.
* feat(code): sync the Code tab and the canvas both ways by patching
Canvas edits now patch the Design JSX a person wrote instead of waiting
for them to leave the editor: each linked element remembers the layer as
Design JSX last wrote it, and a canvas change rewrites only the attributes,
text and child elements that differ from that base, as CodeMirror changes
that keep the cursor, comments, formatting and history. Attributes written
as expressions are never overwritten; the code marks them when the canvas
now differs. Untouched code is regenerated with a minimal text change.
Code edits update layers in place: the new render is reconciled into the
existing layers (reconcileRenderedLayers), which keep their ids, so links,
selection and canvas edits survive typing. Each edit is one coalesced undo
step, replacing the restore-and-rerender preview and the commit on blur.
* feat(code): patch reordered layers and aliased properties in edited code
Reordering layers on the canvas now moves their elements in code a person
wrote: each child element and the blank lines and comments above it form a
block kept as written, and the children are written again in the new
order, staying linked. Children that cannot move safely, such as a loop
between them, keep their order and are marked.
Properties accepted under several names now come from one alias table in
the Design JSX schema, which the renderer resolves through and the patcher
and issue underlines use, so a canvas change to `w` patches `width` where
the person wrote that, instead of adding a second attribute.
* feat(code): keep the cursor in moved code and patch values written in style
A reorder rewrites the children span in one change, which collapsed a
cursor or out-of-sync marker inside a moved element to the span's edge.
The patch now carries where each block moved and places selections and
markers inside it at their new position.
Properties the renderer also reads from style={{ … }} come from a table in
the Design JSX schema instead of a hand-written list, keeping the rule
that an attribute under any of its names wins. The patcher uses it to
update a value written in style where it is, as a number or a px string
as written; values the renderer cannot read, such as '50%', are marked.
The layer patcher is split by concern: syntax helpers, attribute and
style patches, child patches, out-of-sync state and transaction assembly.
* feat(code): show the code's layer on the canvas as a tinted box
The layer of the element around the cursor used the canvas hover slot, so
moving the pointer over the canvas replaced it and the two read the same.
It now has its own shared editor state, codeFocusNodeId, drawn as the hover
outline over a light tint in every pane: hover stays an outline and the
selection keeps its handles, without borrowing the dashed outlines that
already mean component sets, drag parents and ghosts.
* fix(code): write added and removed layers when a reorder cannot move the code
When children could not be moved, such as two written on one line, the
patch marked the order and returned before adding or removing elements,
so a layer created in the same change never reached the code. It now
marks the order and still writes additions and removals.
* refactor(design-jsx): format the rebased layer description and stroke aliases
* feat(code): mount the layer-linked code editor through useCodeMirror
Master moved the code editor onto the shared useCodeMirror composable.
Its layer links, issue underlines, canvas patches, minimal text updates,
autofocus and read-only cursor now sit on that composable instead of a
hand-mounted view.
|
||
|---|---|---|
| .claude | ||
| .devcontainer | ||
| .github | ||
| .storybook | ||
| .vscode | ||
| assets/brand | ||
| desktop | ||
| lint | ||
| packages | ||
| public | ||
| skills/open-pencil | ||
| src | ||
| tests | ||
| tools | ||
| vite | ||
| .coderabbit.yaml | ||
| .gitattributes | ||
| .gitignore | ||
| .gitleaks.toml | ||
| .lfsconfig | ||
| .oxfmtrc.json | ||
| AGENTS.md | ||
| bun.lock | ||
| bunfig.toml | ||
| CHANGELOG.md | ||
| commitlint.config.ts | ||
| CONTRIBUTING.md | ||
| index.html | ||
| knip.json | ||
| LICENSE | ||
| oxlint.json | ||
| package.json | ||
| playwright.config.ts | ||
| portless.json | ||
| README.md | ||
| SECURITY.md | ||
| steiger.config.ts | ||
| tsconfig.json | ||
| tsconfig.node.json | ||
| vite.config.ts | ||
| wdio.conf.ts | ||
OpenPencil
Open-source design editor. Opens .fig and .pen design files, includes built-in AI, and ships as a programmable toolkit with a headless Vue SDK for building custom editors.
Status: Active development. Usable today, with some rough edges as features evolve.
Try it online → · Download · Documentation · Roadmap · llms.txt
Installation
macOS (Homebrew):
brew install --cask openpencil
Or download from the releases page, or use the web app — no install needed.
Requires macOS 13 or later with current Safari updates, Windows 10 or later, or Linux with WebKitGTK 2.40+; the web app needs Chrome 111, Edge 111, Firefox 128, or Safari 16.4 or later. See system requirements.
What it does
- Opens
.figand.penfiles — read and write native Figma files, open supported Pencil documents from the app or OS file browser, copy & paste nodes between apps - AI builds designs — describe what you want in chat, 100+ tools create and modify nodes. Connect OpenRouter, Anthropic, OpenAI, Google AI, DeepSeek, Z.ai, MiniMax, or compatible endpoints
- Fully programmable — headless CLI, XPath queries, Figma Plugin API via
eval, MCP server for AI agents, and desktop agent integrations for Claude Code, Codex, and Gemini CLI - Lint, convert, and extract tokens — inspect documents, lint naming/layout/accessibility, convert between supported formats, analyze colors/typography/spacing/clusters, and extract design tokens
- Components and variants — create reusable components, group variants into component sets, insert local assets as instances, and switch variants from the inspector
- Image vectorization — convert image layers into editable vector layers with Recraft or fal.ai
- Design-to-code export — export selections as JSX/Tailwind, generate token outputs, and map designs into component-oriented code workflows
- Vue SDK for custom editors — headless components and composables for embedding OpenPencil into other apps or building workflow-specific editing surfaces. Read the SDK docs →
- Real-time collaboration — P2P via WebRTC, no server, no account. Cursors, presence, follow mode
- Auto layout & CSS Grid — flex and grid layout via Yoga WASM, with gap, padding, alignment, track sizing
- ~15 MB desktop app — Tauri v2 for macOS, Windows, Linux. Also runs in the browser as a PWA
CLI
npm install -g @open-pencil/cli
# or: bun add -g @open-pencil/cli
Inspect design files
Browse node trees, search by name or type, dig into properties — all without opening the editor:
openpencil tree design.fig
openpencil find design.pen --type TEXT
openpencil node design.fig --id 1:23
openpencil info design.fig
[0] [page] "Getting started" (0:46566)
[0] [section] "" (0:46567)
[0] [frame] "Body" (0:46568)
[0] [frame] "Introduction" (0:46569)
[0] [frame] "Introduction Card" (0:46570)
[0] [frame] "Guidance" (0:46571)
Query with XPath
Use XPath selectors to find nodes by type, attributes, and structure:
openpencil query design.fig "//FRAME" # All frames
openpencil query design.fig "//FRAME[@width < 300]" # Frames under 300px
openpencil query design.fig "//TEXT[contains(@name, 'Button')]" # Text with 'Button' in name
openpencil query design.fig "//*[@cornerRadius > 0]" # Rounded corners
openpencil query design.fig "//SECTION//TEXT" # Text inside sections
Export
Render to PNG, JPG, WEBP, SVG, PDF, PPTX, HTML, JSX, Storybook stories, or .fig — or export selections/pages as .fig and convert whole documents between supported formats:
openpencil export design.fig # PNG
openpencil export design.fig -f jpg -s 2 -q 90 # JPG at 2x, quality 90
openpencil export design.fig -f fig --page "Page 1" # Export a page as .fig
openpencil export design.fig -f jsx --style tailwind # Tailwind JSX
openpencil export design.fig -f html --css tailwind # Tailwind HTML fragment
openpencil export design.fig -f html --html standalone --assets external # HTML + assets
openpencil export design.fig -f storybook --framework vue # Storybook stories per component
openpencil convert design.pen output.fig # Convert between document formats
openpencil import page.html --css styles.css -o page.fig # HTML/CSS → editable .fig
DOM/CSS input flows through @open-pencil/dom-css, so HTML, authored CSS, and Tailwind utility CSS can become editable OpenPencil layers:
openpencil import card.html --css card.css -o card.fig
openpencil import card.html --tailwind "flex flex-col gap-3 w-80 p-6 rounded-xl bg-white" -o card.fig
<div className="flex flex-col gap-4 p-6 bg-white rounded-xl">
<p className="text-2xl font-bold text-[#1D1B20]">Card Title</p>
<p className="text-sm text-[#49454F]">Description text</p>
</div>
Lint design files
Catch naming, layout, structure, and accessibility issues from the terminal:
openpencil lint design.fig
openpencil lint design.pen --preset strict
openpencil lint design.fig --rule color-contrast
openpencil lint design.fig --list-rules
Analyze and extract design tokens
Audit an entire design system from the terminal — find inconsistencies, extract the real palette, and spot components waiting to be extracted:
openpencil analyze colors design.fig
openpencil analyze typography design.fig
openpencil analyze spacing design.fig
openpencil analyze clusters design.fig
openpencil analyze overlaps design.fig
openpencil variables design.fig
#1d1b20 ██████████████████████████████ 17155×
#49454f ██████████████████████████████ 9814×
#ffffff ██████████████████████████████ 8620×
#6750a4 ██████████████████████████████ 3967×
3771× frame "container" (100% match)
size: 40×40, structure: Frame > [Frame]
2982× instance "Checkboxes" (100% match)
size: 48×48, structure: Instance > [Frame]
Script with Figma Plugin API
eval gives you the full Figma Plugin API. Modify the file, write it back:
openpencil eval design.fig -c "figma.currentPage.children.length"
openpencil eval design.fig -c "figma.currentPage.selection.forEach(n => n.opacity = 0.5)" -w
Control the running app
When the desktop app is running, omit the file argument — the CLI connects via RPC and operates on the live canvas. Useful for automation scripts, CI pipelines, or AI agents that need to interact with the editor:
openpencil tree # Inspect the live document
openpencil export -f png # Screenshot the current canvas
openpencil eval -c "figma.currentPage.name" # Query the editor
All commands support --json for machine-readable output.
AI & MCP
Built-in chat
Press ⌘J (CtrlJ on Windows and Linux) to open the AI assistant. It has 100+ tools that can create shapes, set fills and strokes, manage auto-layout, work with components and variables, run boolean operations, analyze design tokens, and export assets. Bring your own API key for OpenRouter, Anthropic, OpenAI, Google AI, DeepSeek, Z.ai, MiniMax, or compatible endpoints. No backend, no account.
Not every provider works in the browser, and not every model streams tool calls correctly. See BYOK provider & model compatibility for measured results — contributions welcome.
Coding agents (desktop)
Use Claude Code, Codex, or Gemini CLI directly in the chat panel. The agent connects to the editor's MCP server and uses all 100+ design tools. Requires the desktop app and the agent CLI installed locally.
Pi is also available as an optional AI SDK Harness provider. Install its companion CLI with npm install -g @open-pencil/harness, then add a Pi model profile in Settings → AI & agents. The companion is installed separately so OpenPencil does not bundle a JavaScript runtime for users who do not enable Harness providers.
Setup (Claude Code):
- Install the ACP adapter:
npm install -g @agentclientprotocol/claude-agent-acp - Add MCP permission to
~/.claude/settings.json:{ "permissions": { "allow": ["mcp__open-pencil__*"] } } - Open the desktop app → ⌘J → select Claude Code from the provider dropdown
MCP server
Connect Claude Code, Cursor, Windsurf, or any MCP client to inspect, modify, and export design documents headlessly. 100+ tools. Full docs →
Stdio (Claude Code, Cursor, Windsurf):
npm install -g @open-pencil/mcp
claude mcp add --scope user open-pencil -- openpencil-mcp
For other MCP clients:
{
"mcpServers": {
"open-pencil": {
"command": "openpencil-mcp"
}
}
}
HTTP (scripts, CI):
openpencil-mcp-http # Unix socket on macOS/Linux + http://127.0.0.1:7600/mcp
Local clients discover the private Unix socket automatically and fall back to localhost TCP. Set PORT=0 to disable TCP on macOS/Linux.
File access: Set OPENPENCIL_MCP_ROOT to scope file operations (open_file, new_document, export path param) to a directory. Defaults to the current working directory.
AI agent skill
Teach your AI coding agent to use OpenPencil — inspect designs, export assets, analyze tokens, modify .fig files:
npx skills add open-pencil/open-pencil
Works with Claude Code, Cursor, Windsurf, Codex, and any agent that supports skills.
For documentation-aware agents, the docs site publishes llms.txt, llms-full.txt, and per-page Markdown files generated from the VitePress docs.
Collaboration
Share a link to co-edit in real time. No server, no account — peers connect directly via WebRTC.
- Click the share button in the top-right panel
- Share the generated link (
app.openpencil.dev/share/<room-id>) - Collaborators see your cursor, selection, and edits in real time
- Click a peer's avatar to follow their viewport
Why
Figma is a closed platform that actively fights programmatic access. Their MCP server is read-only. figma-use added full read/write automation via CDP — then Figma 126 killed CDP. Your design files are in a proprietary binary format that only their software can fully read. Your workflows break when they decide to ship a point release.
OpenPencil is the alternative: open source (MIT), reads .fig files natively, every operation is scriptable, and your data never leaves your machine.
See the roadmap for product direction and current Figma compatibility gaps.
Community
- Discord — chat, quick questions, and showing a problem live
- GitHub Discussions — Q&A for help, Ideas for feature proposals, Show and tell for what you built; maintainers post Announcements there
- Issues — reproducible bugs; report security problems through a private advisory
Contributing
bun install
bun run dev:portless # Web editor at https://open-pencil.localhost
bun run tauri dev # Desktop app (requires Rust)
CONTRIBUTING.md covers setup, quality gates, pull requests, and commits. AGENTS.md maps the repository and links the guide inside each package. Desktop builds need Rust and the Tauri v2 prerequisites; run bun run tauri build.
Tech stack
| Layer | Tech |
|---|---|
| Rendering | Skia (CanvasKit WASM) |
| Layout | Yoga WASM (flex + grid via fork) |
| UI | Vue 3, Reka UI, Tailwind CSS 4 |
| File format | Kiwi binary + Zstd + ZIP |
| Collaboration | Trystero (WebRTC P2P) + Yjs (CRDT) |
| Desktop | Tauri v2 |
| AI/MCP | Vercel AI SDK (multi-provider BYOK), MCP SDK, Hono |
Acknowledgments
Thanks to @sld0Ant (Anton Soldatov) for creating and maintaining the documentation site.
License
OpenPencil is licensed under the MIT License.
Copyright (c) 2026 Danila Poyarkov and OpenPencil contributors.
