openpencil/packages/docs/user-guide/components.md
Danila Poyarkov b0e0b321e9
fix: share Figma's creation and grouping between editor and plugin API (#919)
* fix(core): start new layers with Figma's defaults in the editor and plugin API

The plugin API created bare nodes: frames, components, and shapes without fills, and lines and vectors without strokes, so scripts written for Figma drew nothing. Drawn lines also had a black fill instead of a stroke and were invisible. Both paths now share newLayerDefaults, recorded from Figma desktop 126: frames and components white with frames clipping their content, shapes #D9D9D9, lines and vectors a black 1 px stroke, text black. A stroke a script adds gets the 1 px default weight, and an empty vector has no render bounds.

* fix(core): combine variants as Figma does from the canvas and from scripts

The plugin API and the editor command each built component sets their own way, both with 40 px of padding and a grey fill. Figma's command pads the variants by 20 and outlines the set with a 1 px dashed #8A38F5 stroke; its plugin API wraps them exactly with no fill or stroke. One variantSetProps now places and styles the set for both, with a canvas or script style, and applyVariantProperties derives variant properties for both.

* fix(core): report group children in their container's space in the plugin API

Figma's plugin API places children of groups and booleans relative to the nearest real container and refits a group whenever a script changes one of its children. Ours reported group-relative positions and never refit, so scripts placing layers inside groups landed them in the wrong place. x, y, and relativeTransform now map through the groups around a node, and geometry changes, appendChild, insertChild, and remove refit the surrounding groups. The refit moves to Scene Graph as fitEnclosingGroups, shared by the canvas (with undo) and the plugin API.

* test(core): pass script-style strokes and typed components in parity tests

* test(e2e): expect Figma's default shape grey in the scene freshness spec

* fix(vue): draw lines by length and angle as Figma does

The Line tool sized a line as the box spanned by the drag. With the stroke a new line now gets, that box drew as a rectangle outline. A line now starts at the press point with the drag length as its width, no height, and the drag angle as its rotation, as Figma's Line tool makes it; Shift snaps the angle to 45° steps, as the docs already described, and a click makes a 100 px horizontal line.

* fix(core): give each new layer its own copy of the default paints

The defaults spread each paint shallowly, so every layer shared the colour object of the module-level default and editing one layer's colour in place changed the next new layer. Copy the paints with the Scene Graph copy helpers.

* fix(core): group, ungroup, and combine layers through shared code in the plugin API

The plugin API wrapped layers, ungrouped, made booleans, and made components from layers with its own code. Ungroup moved the children to the top of the stack, booleans were named "Boolean union", and a component made from a frame cloned its children under new ids. These now run through the editor's shared wrap, ungroup, and boolean functions, with the placement and defaults recorded in Figma desktop 126: a group or boolean without an index goes on top, ungrouped children take the group's place, booleans are named after the operation and filled with the default grey, a frame becomes a component in its place with its children, and any other layer is wrapped in a white component named after it. Undoing a wrap in the editor now returns each layer to its own place in the stack.

* fix(core): group, frame, combine, and make components from the canvas as Figma does

Recorded in Figma desktop 126: a container made from the canvas takes the topmost selected layer's place, Frame selection adds no fill and does not clip, a component wrapped around layers is white and takes a single layer's name, and a boolean is filled like its topmost operand, or its base for Subtract, without strokes. The canvas commands and the plugin API now share the wrap parent check, stack ordering, component rules, and boolean paints, and the plugin API's createComponentFromNode converts groups in place as Figma does. Undoing a boolean returns each operand to its own place in the stack.

* refactor(core): reuse translate when centering pasted layers
2026-10-06 12:28:52 +00:00

8.1 KiB

title description
Components Creating reusable components, instances, component sets, overrides, and live sync in OpenPencil.

Components

Components are reusable design elements. Edit the main component and all its instances update automatically.

Browse Components

Open the Assets tab in the left panel to browse local components and enabled libraries. Use grid or list view, search by component name, and select a component to see its details. You can insert an asset by clicking it, pressing Enter, or dragging it onto the canvas.

Local assets are grouped by source page. Published library assets remain available when their revision has been downloaded, including when the remote provider is temporarily offline.

Creating a Component

Select a frame or group and press ⌥⌘K (Ctrl + Alt + K). The selection becomes a reusable component.

Any other layer, or several layers, is wrapped in a new white component at their bounding box, in the topmost layer's place in the layer list; a single wrapped layer gives the component its name.

Components display a purple label with a diamond icon above them.

Component Sets and Variants

Select two or more components and press ⇧⌘K (Shift + Ctrl + K) to combine them into a component set — a container with a dashed purple border and 20 px padding around its children, as in Figma. Sets made by scripts with figma.combineAsVariants() wrap their components exactly, as Figma's plugin API does.

Each component in a set can define values across multiple variant dimensions, such as Size=Small, State=Hover, and Theme=Dark. OpenPencil supports sparse combinations, so a set does not need every possible combination. The top-left variant is the default and is used as the fallback when an update no longer contains an exact combination.

Use the component properties panel to add, rename, reorder, and remove variant dimensions and values. Duplicate combinations are rejected.

Component Properties

Components and component sets support reusable text, boolean visibility, and instance-swap properties. Link a property to a descendant field, then select an instance to edit its assigned value without detaching it. Properties and assignments are preserved when saving and reopening .fig files.

Component Libraries

A component library publishes reusable components as an immutable revision. Each published asset has stable library, asset, and revision identity, so different instances can remain on different revisions until you explicitly update them.

Publish a Library

  1. Create the components and component sets you want to share.
  2. Open Assets, then select Manage libraries.
  3. Select Publish library.
  4. Enter a stable library ID and display name. The library ID is locked after the first publication.
  5. Optionally search the change list and enter a revision description.
  6. Select the added, modified, renamed, or removed assets to include.
  7. Confirm the destination and select Publish library.

On later publications, unchecked changes remain pending. Unchanged assets keep their previous published definitions, and removed definitions remain available while documents still reference their historical revision.

Enable and Insert Library Assets

Open Assets → Manage libraries to enable a published library. Its components appear in the Assets panel alongside local components. Insert one by clicking it, using the keyboard, or dragging it onto the canvas.

Published definitions are read-only in consuming documents. Edit the source document and publish another revision to change a definition. Instances linked to those definitions remain editable through their component properties and overrides.

Review and Accept Updates

Open Manage libraries → Updates to discover newer revisions. Discovery does not modify the document. You can review the current and updated instance side by side, navigate between affected instances, and then update:

  • The selected instance
  • All instances of one asset
  • Instances on the current page
  • Instances across all pages

OpenPencil preserves compatible text, visibility, and instance-swap assignments. If an exact variant no longer exists, the review identifies the top-left fallback before you accept it. Applying an update creates an undo entry.

Local, Storage, and Offline Use

Libraries can use the local browser catalog or a configured storage provider. Remote publication uses immutable revision objects and a conditional latest pointer, preventing two publishers from silently overwriting each other.

Downloaded revisions are cached locally. A document can continue rendering and inserting downloaded definitions while offline. Integrity failures are reported instead of being hidden by cached data.

Saving Consumer Documents

Enabled-library bindings and materialized definitions are saved with .fig documents. Reopening a consumer file preserves its linked instances and revision identities, even when its remote library is unavailable.

Creating Instances

Right-click a component and select Create instance from the context menu. The instance appears 40 px to the right of the source component, visually identical.

Instance creation is available only through the context menu — there's no toolbar button.

Detaching an Instance

Select an instance and press ⌥⌘B (Ctrl + Alt + B) to detach it. The instance becomes a regular frame with no link to the original component. All overrides are baked in.

Go to Main Component

Right-click an instance and select Go to main component. The editor navigates to and selects the main component, switching pages if needed.

Live Sync

When you edit a component, all its instances update automatically. Synced properties include:

  • Width and height
  • Fills, strokes, and effects
  • Opacity and corner radii
  • Layout properties (auto layout settings)
  • Clips content setting

Sync triggers automatically after node updates, moves, and resizes within a component.

Overrides

Instances can override specific properties without breaking the sync link. When a property is overridden on an instance, that property is skipped during sync — other properties continue to update from the main component.

Overridable Properties

Child-level overrides support: name, text, font size, font weight, font family, plus all visual and layout properties (fills, strokes, effects, opacity, corner radii, size).

New Children

When you add a child to a component, all existing instances gain a cloned copy automatically. Child order in instances always matches the component.

Hit Testing

Components and instances are opaque containers — clicking on a child selects the component itself, not the child. Double-click to enter the component and select children inside it.

Visual Treatment

Element Appearance
Component label Purple with diamond icon, always visible
Instance label Purple with diamond icon, always visible
Component set border Dashed purple outline

Keyboard Shortcuts

Action Mac Windows / Linux
Create component ⌥⌘K Ctrl + Alt + K
Create component set ⇧⌘K Shift + Ctrl + K
Detach instance ⌥⌘B Ctrl + Alt + B

Tips

  • Editing text inside an instance creates an override — the text won't be overwritten when the component changes.
  • Use component sets to organize multidimensional variants such as size, state, and theme.
  • Publish reusable assets from their source document; published definitions are intentionally read-only in consumer documents.
  • Review updates before accepting them when a revision removes an exact variant combination.
  • See Context Menu for all component-related actions.