# Features ## Why OpenPencil Design tools are a supply chain problem. When your tool is closed-source, the vendor controls what's possible — they can break your automation overnight. OpenPencil is an open-source alternative: MIT-licensed, Figma-compatible, fully local, and programmable. ## Figma .fig File Import & Export Open and save native Figma files directly. Import decodes the full 194-definition Kiwi schema including NodeChange messages with ~390 fields. Export encodes the scene graph back to Kiwi binary with Zstd compression and thumbnail generation. Save (⌘S) and Save As (⇧⌘S) use native OS dialogs on the desktop app. The import/export pipeline supports round-trip fidelity. ## Copy & Paste with Figma Select nodes in Figma, ⌘C, switch to OpenPencil, ⌘V — they appear with fills, strokes, auto-layout, text, corner radii, effects, and vector networks preserved. Works the other way too: copy from OpenPencil, paste into Figma. Under the hood, both directions use the same Kiwi binary format as .fig files — Figma base64-encodes it into HTML on the clipboard. OpenPencil decodes the full schema on paste (194 definitions, ~390 fields per NodeChange) and encodes it on copy. Vector data round-trips through the `vectorNetworkBlob` binary format. Also works between OpenPencil instances via a separate native clipboard format. Paste handles complex scenarios: vector paths are scaled from Figma's `normalizedSize` to actual node bounds, instance children are populated from their component's `symbolData`, component sets are detected by promoting frames with variant `componentPropDefs`, internal canvas nodes are skipped, and `symbolOverrides` are applied for text, fills, visibility, and layout properties. Fonts referenced by pasted text nodes are automatically loaded. ## Vector Networks The pen tool uses Figma's vector network model — not simple paths. Click to place corner points, click+drag for bezier curves with tangent handles. Supports open and closed paths. Vector data uses the same `vectorNetworkBlob` binary format as Figma. ## Shape Tools The toolbar provides all basic Figma shape tools: Rectangle (R), Ellipse (O), Line (L), Polygon, and Star. Polygon and Star are in the shapes flyout — click and hold the Rectangle tool to access them. Polygon draws regular polygons (default 3 sides) using a `pointCount` property. Star draws pointed stars (default 5 points) with a configurable `starInnerRadius` (default 0.38). All shapes support fill, stroke, hover highlight, and selection outline. ## Auto-Layout Yoga WASM provides CSS flexbox layout. Frames support: - **Direction** — horizontal, vertical, wrap - **Gap** — spacing between children - **Padding** — uniform or per-side - **Justify** — start, center, end, space-between - **Align** — start, center, end, stretch - **Child sizing** — fixed, fill, hug Shift+A toggles auto-layout on a frame or wraps selected nodes. ## Inline Text Editing Canvas-native text editing — no DOM textarea overlay on screen. A `TextEditor` class in `@open-pencil/core` handles cursor positioning, text selection, word boundary detection, and line navigation using the CanvasKit Paragraph API (`getGlyphPositionAtCoordinate`, `getRectsForRange`, `getLineMetrics`). A hidden phantom textarea captures keyboard input, IME composition, and clipboard events. Double-click a text node to enter edit mode. The canvas renders a blinking caret, translucent blue selection rectangles, and a blue outline around the node. Click and drag to select text, double-click a word to select it, triple-click to select all. Keyboard navigation with modifier support: ⌥←/→ for word movement, ⌘←/→ for line start/end, ⌥⌫ for word delete, ⌘⌫ for line delete. Shift extends selection. Esc or clicking outside commits the edit. **Font picker** with virtual scroll (reka-ui ListboxVirtualizer), search filter, and CSS font preview — each font name renders in its own typeface. In Tauri, system fonts are enumerated via Rust `font-kit` crate (`list_system_fonts`/`load_system_font` commands) with OnceLock caching for instant picker access. In browser, the Local Font Access API is used when available. ## Rich Text Formatting Per-character formatting within a single text node. Select text and press ⌘B for bold, ⌘I for italic, ⌘U for underline, or use the B/I/U/S buttons in the Typography section. With no selection, the shortcut toggles the whole-node style. Implemented via a StyleRun model — an array of `{start, length, style}` segments where style includes fontWeight, italic, and textDecoration. The renderer uses CanvasKit ParagraphBuilder.pushStyle/pop to render mixed formatting in a single paragraph. Style runs adjust automatically on insert and delete to preserve formatting boundaries. Rich text formatting is preserved during .fig import/export — `characterStyleIDs` and `styleOverrideTable` from Figma's TextData are imported as StyleRun arrays and exported back with a deduped style table. ## Undo/Redo Every operation is undoable — node creation/deletion, moves, resizes, property changes, reparenting, layout changes, and all variable operations (create/delete/rename variables, create/rename collections, color and value changes). The system uses an inverse-command pattern — before applying any change, it snapshots affected fields. The snapshot becomes the inverse. ⌘Z undoes, ⇧⌘Z redoes. ## Snap Guides Edge and center snapping with red guide lines when nodes align. Rotation-aware — snap calculations use actual visual bounds of rotated nodes. Coordinates are computed in absolute canvas space. ## Canvas Rulers Rulers at the top and left edges show coordinate scales. When you select a node, rulers highlight its position with a translucent band and show coordinate badges at the start/end points. ## Color Picker & Fill Types HSV color selection with hue slider, alpha slider, hex input, and opacity control. The fill type picker provides tabs for Solid, Gradient (Linear, Radial, Angular, Diamond), and Image. Switching to a gradient type shows an editable gradient stop bar. Gradient transforms position the gradient within the shape. Connected to fill and stroke sections in the properties panel. ## Layers Panel Tree view of the document hierarchy using Reka UI Tree component. Expand/collapse frames, drag to reorder (changes z-order), toggle visibility per node. Both the layers panel and properties panel are resizable — drag the edge between panels and canvas to adjust width (default 15%, range 10–30%). Layout persists across reloads. ## Properties Panel Tabbed interface with **Design** | **Code** | **AI** tabs (reka-ui Tabs). The **Design** tab is context-sensitive with sections: - **Appearance** — opacity, corner radius (uniform or per-corner with independent toggle), visibility - **Fill** — solid/gradient/image type picker, gradient stop editor, hex input, opacity - **Stroke** — color, weight, opacity, cap, join, dash pattern - **Effects** — add/remove effects, type picker (drop shadow, inner shadow, layer blur, background blur, foreground blur), inline expanded controls (offset, blur, spread, color for shadows; blur radius for blurs), per-effect visibility toggle - **Typography** — font family (FontPicker with virtual scroll and search), weight, size, alignment, B/I/U/S buttons - **Layout** — auto-layout controls when enabled - **Position** — alignment buttons, rotation, flip - **Export** — scale, format (PNG/JPG/WEBP), live preview, multi-export - **Page** — canvas background color (shown when no nodes selected) The **Code** tab shows JSX export of the selection (see [Code Panel](#code-panel)). The **AI** tab provides an AI chat interface (see [AI Chat](#ai-chat)). ## Group/Ungroup ⌘G groups selected nodes. ⇧⌘G ungroups. Nodes are sorted by visual position when grouping to preserve reading order. ## Sections Sections (S) are top-level organizational containers on the canvas. Each section displays a title pill with the section name. Title text color automatically inverts based on the pill's background luminance for readability. Creating a section auto-adopts overlapping sibling nodes. Frame name labels are shown for direct children of sections. ## Multi-Page Documents Documents support multiple pages like Figma. The pages panel lets you add, delete, and rename pages. Each page maintains independent viewport state (pan, zoom, background color). Double-click a page name to rename inline. ## Hover Highlight Nodes highlight on hover with a shape-aware outline that follows the actual geometry — ellipses get elliptical outlines, rounded rectangles get rounded outlines, vectors get path outlines. This provides visual feedback before clicking to select. ## Advanced Rendering (Tier 1) The CanvasKit renderer supports full Tier 1 visual features for Figma rendering parity: - **Gradient fills** — linear, radial, angular, diamond with gradient stops and transforms - **Image fills** — decoded from blob data with scale modes (fill, fit, crop, tile) - **Effects** — drop shadow, inner shadow, layer blur, background blur, foreground blur - **Stroke properties** — cap (none, round, square, arrow), join (miter, bevel, round), dash patterns - **Arc data** — partial ellipses with start/end angle and inner radius (donuts) - **Viewport culling** — off-screen nodes are skipped during rendering - **Paint reuse** — Skia Paint objects are recycled across frames instead of reallocated - **RAF coalescing** — multiple render requests within one frame are batched into a single `requestAnimationFrame` call ## Components & Instances Create reusable components from frames or selections (⌥⌘K). A single frame converts in-place to a COMPONENT; multiple nodes wrap in a new component. Combine multiple components into a COMPONENT_SET (⇧⌘K) with a dashed purple border. Create instances from components via context menu — instances copy the component's visual properties and deep-clone children with `componentId` mapping. Detach an instance back to a frame with ⌥⌘B. "Go to main component" navigates to and selects the source component, switching pages if needed. **Live sync:** Editing a main component propagates changes to all its instances automatically. The store triggers sync after property updates, moves, and resizes. Synced properties include size, fills, strokes, effects, opacity, corner radii, layout, and clipsContent. Instance children are matched to component children via `componentId`. **Override support:** Instances maintain an overrides record. Properties marked as overridden are preserved during sync — if you customize an instance child's text, it won't be overwritten when the component changes. New children added to a component appear in all existing instances. Components and instances display always-visible purple labels with a diamond icon showing the node name. They act as opaque containers for selection — clicking selects the component itself, double-clicking enters it to select children. ## Variables Design tokens as variables with collections and modes. Open the variables dialog from the Variables section in page properties (settings icon). The dialog uses TanStack Table (`@tanstack/vue-table`) with resizable columns — Name | Mode 1 | Mode 2 | ... — matching Figma's table layout. Collection tabs with double-click to rename, search bar, and "+ Create variable" button. Color variables show inline ColorInput with picker. Supports COLOR type with full UI, FLOAT/STRING/BOOLEAN types defined. Organize variables in collections (e.g., "Primitives", "Semantic"), define modes (e.g., Light/Dark), switch active mode. Bind variables to fill colors via the variable picker in Fill — bound fills show a purple badge with the variable name and a detach button. Alias chains (one variable references another) with cycle detection. All variable operations are undoable: create/delete variable, create collection, rename, color change. The demo document includes three collections: Primitives (9 colors with Light/Dark modes), Semantic (aliases to Primitives), and Spacing (8 number tokens with Default/Compact modes). Variables are bound to demo nodes for live preview. ## Image & SVG Export Export selected nodes as PNG, JPG, WEBP, or SVG. The Export section in the properties panel provides scale selection (0.5×–4×, hidden for SVG), format picker, multi-export support, and a live preview with checkerboard background. Also available via context menu "Export…" and ⇧⌘E. SVG export supports rectangles, ellipses, lines, stars, polygons, vectors, text with style runs, gradients, image fills, effects, blend modes, and nested groups. CLI: `bun open-pencil export --format svg file.fig`. MCP/AI tool: `export_svg`. ## Copy/Paste as The context menu **Copy/Paste as** submenu exposes multiple clipboard formats: - **Copy as text** — visible text content from the selection - **Copy as SVG** — full SVG markup of the selection - **Copy as PNG** — renders at 2× to the system clipboard (⇧⌘C) - **Copy as JSX** — OpenPencil JSX compatible with `renderJsx()` ## Stroke Align & Per-Side Weights Stroke alignment controls where the stroke is drawn relative to the shape boundary: **Inside** (stroke painted inside, clip path trims overlap with fill), **Center**, or **Outside**. Rendering matches Figma's behavior exactly. Individual stroke weights per side let you set different widths for Top, Right, Bottom, and Left independently via the side selector dropdown in the Stroke section. ## Context Menu Right-click on the canvas opens a Figma-style context menu. Actions adapt to the current selection: - **Clipboard** — Copy, Cut, Paste here, Duplicate, Delete - **Copy/Paste as** — Copy as text, SVG, PNG (⇧⌘C), JSX - **Z-order** — Bring to front, Send to back - **Grouping** — Group, Ungroup, Add auto layout - **Components** — Create component, Create component set, Create instance, Go to main component, Detach instance (purple-styled items) - **Visibility** — Hide/Show, Lock/Unlock - **Move to page** — submenu with all other pages Right-clicking a node selects it first. Right-clicking empty canvas clears selection. ## Z-Order, Visibility & Lock ] brings selected nodes to front, [ sends to back within their parent. ⇧⌘H toggles visibility — hidden nodes stay in the layers panel but don't render. ⇧⌘L toggles lock — locked nodes can't be selected or moved from the canvas. Move nodes between pages via the context menu's "Move to page" submenu. ## Web & Desktop App OpenPencil runs in the browser at [app.openpencil.dev](https://app.openpencil.dev) — no installation required. The desktop app uses a Tauri v2 shell (~5MB vs Electron's ~100MB). Works fully offline — no account, no server, no internet required. Native menu bar with File/Edit/View/Object/Window/Help menus on all platforms. macOS gets an app-level submenu. Native Save/Open dialogs via Tauri plugin-dialog. Zstd compression offloaded to Rust for .fig export performance. Developer Tools accessible via ⌘⌥I. ## App Menu (Browser) In browser mode, a menu bar built with reka-ui Menubar provides access to all major editor actions. Six menus: **File** (Open, Save, Save As, Export selection), **Edit** (Undo, Redo, Copy, Paste, Duplicate, Delete, Select all), **View** (Zoom to fit, Zoom in/out, Toggle rulers), **Object** (Group, Ungroup, Frame selection, Create component, Create component set), **Text** (Font size adjustment, Bold, Italic, Underline, Strikethrough, Align submenu), **Arrange** (Bring to front, Send to back, Bring forward, Send backward). Keyboard shortcuts are displayed next to each menu item with platform-aware modifier labels (⌘ on Mac, Ctrl+ on Windows/Linux). Hidden when running in Tauri, which provides its own native menus. ## Autosave Files are automatically saved 3 seconds after the last scene change. A debounced watcher monitors `sceneVersion` — multiple rapid edits only trigger a single write after activity settles. Uses the Tauri fs plugin on desktop or the File System Access API in supported browsers. Autosave is disabled for new untitled documents until the user performs an explicit Save As. Errors are handled silently — the user can always trigger a manual save with ⌘S. **Auto-save toggle** — File → Auto-save disables automatic writes when you want full manual control (useful during exploratory edits you may not want to persist). ## Mobile Layout & PWA OpenPencil is installable as a Progressive Web App on mobile devices. The responsive layout adapts to small screens: - Side panels replaced by a **swipeable bottom drawer** with tabs: Layers, Properties, Design, Code - Toolbar collapses to a compact strip with animated category switching - Spring-animated drawer with pan gesture control - PWA manifest with icons and service worker for offline capability (disabled in dev mode) ## Tailwind CSS v4 JSX Export The Code panel's format toggle switches between two output modes: - **OpenPencil** — custom component tree (`Frame`, `Text`, `Rect`, etc.) compatible with `renderJsx()` - **Tailwind CSS v4** — HTML with utility classes (`