The .pen Format

Pencil stores design documents as .pen files. Under the hood, each file contains JSON describing an object tree — similar in concept to HTML or SVG — that represents every layer, frame, and component in your design.

Object Tree & Types

Every object in the tree carries a unique id and a type that determines how it renders. Common types include rectangle, frame, text, ellipse, vector, and group. Objects can contain children to form a nested hierarchy.

{
  "id": "doc-root",
  "type": "document",
  "children": [
    {
      "id": "frame-1",
      "type": "frame",
      "x": 0,
      "y": 0,
      "width": 1440,
      "height": 900,
      "children": [
        {
          "id": "rect-1",
          "type": "rectangle",
          "width": 200,
          "height": 100
        }
      ]
    }
  ]
}

Layout

Top-level objects live on an infinite 2D canvas and are positioned with absolute x and y coordinates. Nested objects are positioned relative to their parent.

Frames support flexbox-style auto-layout through the layout property. When enabled, children are arranged automatically and you can control distribution with justifyContent and alignItems, just like CSS flexbox.

{
  "id": "nav-bar",
  "type": "frame",
  "layout": "horizontal",
  "justifyContent": "space-between",
  "alignItems": "center",
  "padding": 16,
  "gap": 12
}

Graphics

Visual appearance is controlled through three main properties: fill, stroke, and effect. Each property accepts an array, meaning a single object can have multiple fills or strokes painted sequentially.

Fill Types

  • Solid colors (hex, RGBA)
  • Linear gradients
  • Radial gradients
  • Angular (conic) gradients
  • Image fills (fit, fill, crop, tile)
  • Mesh gradients

When multiple fills are present they are painted in array order — first entry on the bottom, last entry on top — allowing complex layered effects with a single object.

"fill": [
  {
    "type": "solid",
    "color": "#7c5cfc"
  },
  {
    "type": "linearGradient",
    "from": { "x": 0, "y": 0 },
    "to": { "x": 1, "y": 1 },
    "stops": [
      { "offset": 0, "color": "#7c5cfc" },
      { "offset": 1, "color": "#00d4ff" }
    ]
  }
]

Components & Instances

Any object can be turned into a reusable component by setting reusable: true. Once defined, instances are created by using the ref type and pointing to the component's id.

Instances support property overrides, allowing you to change fills, text content, visibility, and more without detaching from the source component. Nested components are supported through the descendants property, which maps descendant IDs to their override values.

// Component definition
{
  "id": "btn-primary",
  "type": "frame",
  "reusable": true,
  "children": [
    { "id": "btn-label", "type": "text", "content": "Click me" }
  ]
}

// Instance with overrides
{
  "id": "btn-instance-1",
  "type": "ref",
  "refId": "btn-primary",
  "descendants": {
    "btn-label": { "content": "Submit" }
  }
}

Slots

Slots provide customization points inside components. A frame with the slot property acts as a placeholder that consumers of the component can fill with their own content. This enables flexible, composable design systems where the outer structure is fixed but interior regions are open for replacement.

{
  "id": "card-body",
  "type": "frame",
  "slot": true,
  "children": []
}

Variables & Themes

Documents can define variables that are referenced throughout the object tree. Variables use dotted naming conventions (e.g. colors.primary, spacing.md) for organization.

Theme axes let you define conditional values — for example, a light and dark mode axis where each variable resolves differently depending on the active theme. When multiple themes match, the last matching theme wins.

"variables": {
  "colors.primary": {
    "default": "#7c5cfc",
    "themes": {
      "dark": "#9b7eff",
      "light": "#5a3ed6"
    }
  },
  "colors.background": {
    "default": "#111111",
    "themes": {
      "dark": "#0a0a0a",
      "light": "#ffffff"
    }
  }
}

Last updated: February 22, 2026