Developers

Nodes reference

Every node type in the Desyn document model — base properties, containers, auto-layout, shapes, text, images, SVG, media, charts, tables, QR codes and semantic roles.

On this page

All geometry is in design pixels (1 px = 1/96 in) regardless of a page's display unit. x and y are relative to the parent's top-left corner (before the parent's rotation); rotation is in degrees clockwise around the node's center. The origin is top-left and y grows downward.

Base node#

Every node has these properties; type is the discriminant.

docs/DOCUMENT_MODEL.md
interface BaseNode {
  id: Id;
  type: string;                 // discriminant, see each interface
  name: string;
  parent: Id | null;            // null only for root
  x: number; y: number; width: number; height: number; rotation: number; // degrees
  flipX?: boolean; flipY?: boolean;
  opacity: number;              // 0..1
  blend: BlendMode;
  visible: boolean;
  locked: boolean;
  effects: Effect[];
  role?: Role;                  // semantic role (AI + layout intelligence), see Role
  constraints?: { h: "left" | "right" | "center" | "stretch" | "scale"; v: "top" | "bottom" | "center" | "stretch" | "scale" };
  sizing?: { h: "fixed" | "hug" | "fill"; v: "fixed" | "hug" | "fill" }; // used inside auto-layout parents
  aspectLocked?: boolean;
  mask?: boolean;               // if true this node masks following siblings within its parent until the next mask (alpha mask)
  link?: string;                // url or "page:<pageId>"
  alt?: string;                 // accessibility description
  anim?: Animation[];           // entrance/exit/emphasis animations (video + presentations)
  notes?: string;               // speaker notes / designer notes (only meaningful on pages and frames)
  meta?: Record<string, unknown>; // e.g. { slot: "headline" } for template slots, { aiNote: "..." }
}

mask: true makes a node an alpha mask for the siblings that follow it within the same parent, up to the next mask. link is a URL or page:<pageId>. meta is free-form and preserved; templates use meta.slot to mark replaceable content.

Containers#

Root, pages, frames, groups and booleans have ordered children, back to front (index 0 is the bottom-most).

docs/DOCUMENT_MODEL.md
interface ContainerProps {
  children: Id[];               // ordered back-to-front (index 0 is bottom-most)
  clip: boolean;                // clip children to bounds
  layout?: AutoLayout;
}

interface RootNode extends BaseNode, Pick<ContainerProps, "children"> { type: "root"; }   // x=y=0, size 0, children are PageNode ids

interface PageNode extends BaseNode, ContainerProps {
  type: "page";
  unit: Unit;
  background: Fill[];
  bleed: number;                // px, print bleed outside width/height
  durationMs?: number;          // for video / presentation auto-advance
  transition?: { kind: "none" | "fade" | "slide" | "push" | "zoom" | "dissolve"; durationMs: number };
  grid?: { columns: number; gutter: number; margin: number; baseline?: number }; // layout grid (guides)
  guides?: { axis: "x" | "y"; pos: number }[];
}

interface FrameNode extends BaseNode, ContainerProps, Shaped { type: "frame"; }

interface GroupNode extends BaseNode, ContainerProps { type: "group"; }              // no paint; bounds fit children

interface BooleanNode extends BaseNode, ContainerProps, Shaped { type: "boolean"; op: "union" | "subtract" | "intersect" | "exclude"; }
  • Root has no geometry of its own; its children are the pages, in order.
  • Pages are children of root only and cannot nest. bleed extends the printable area outside the page for print export. grid and guides are editor aids that never render.
  • Groups have no paint; their bounds are derived from their children.
  • Booleans combine their children's outlines with the given operation and paint the result with their own fills and strokes.

Auto-layout#

A frame (or boolean) with layout positions and sizes its children. Children use sizing to hug their content, fill the remaining space, or keep a fixed size.

docs/DOCUMENT_MODEL.md
interface AutoLayout {
  mode: "row" | "column" | "grid";
  gap: number; rowGap?: number;                       // gap = main-axis gap (column gap for grid)
  padding: [number, number, number, number];          // top right bottom left
  mainAlign: "start" | "center" | "end" | "space-between";
  crossAlign: "start" | "center" | "end" | "stretch" | "baseline";
  wrap?: boolean; columns?: number;                   // grid column count
  reverse?: boolean;
}

Engines recompute x, y, width and height of affected nodes after every transaction; clients persist the derived values so that servers and clients without a layout engine see consistent boxes.

Shapes#

docs/DOCUMENT_MODEL.md
interface Shaped {
  fills: Fill[];                // bottom-to-top
  strokes: Stroke[];
  cornerRadius: number | [number, number, number, number]; // tl tr br bl
  cornerSmoothing?: number;     // 0..1 (iOS-style squircle)
}

interface RectNode extends BaseNode, Shaped { type: "rect"; }

interface EllipseNode extends BaseNode, Shaped { type: "ellipse"; arc?: { start: number; end: number; inner: number }; } // degrees, inner ratio 0..1

interface PolygonNode extends BaseNode, Shaped { type: "polygon"; sides: number; }

interface StarNode extends BaseNode, Shaped { type: "star"; points: number; innerRatio: number; }

interface LineNode extends BaseNode { type: "line"; strokes: Stroke[]; startCap: Cap; endCap: Cap; } // drawn along +x of box, height 0

type Cap = "none" | "arrow" | "triangle" | "circle" | "square" | "diamond" | "bar";

interface PathNode extends BaseNode, Shaped {
  type: "path";
  paths: { d: string; fillRule: "nonzero" | "evenodd" }[];   // SVG path data in the node's local box coordinates (0..width, 0..height)
}

cornerRadius is a single value or [topLeft, topRight, bottomRight, bottomLeft]. Path data is SVG path syntax in the node's local box (0..width, 0..height). A line is drawn along the +x axis of its box and has height 0.

Text#

docs/DOCUMENT_MODEL.md
interface TextNode extends BaseNode {
  type: "text";
  runs: TextRun[];              // concatenated text; "\n" starts a new paragraph
  style: TextStyle;             // default style for all runs
  paragraph: ParagraphStyle;
  autoResize: "none" | "height" | "width"; // "height": width fixed, height grows; "width": single line grows both
  maxLines?: number;            // with ellipsis truncation
  curve?: number;               // text-on-arc, degrees of sweep (0 = straight)
  verticalAlign: "top" | "middle" | "bottom";
  strokes?: Stroke[];           // outline text
  styleRef?: Id;                // DesignStyles.text id this node follows
}

interface TextRun { text: string; style?: Partial<TextStyle>; }

interface TextStyle {
  fontFamily: string; fontWeight: number; fontStyle: "normal" | "italic"; fontSize: number;
  fills: Fill[]; letterSpacing: number /* em */; textCase: "none" | "upper" | "lower" | "title";
  decoration: "none" | "underline" | "strikethrough"; link?: string;
  highlight?: Color; features?: string[] /* OpenType e.g. "liga","ss01" */; variations?: Record<string, number>;
}

interface ParagraphStyle {
  align: "left" | "center" | "right" | "justify"; direction: "ltr" | "rtl" | "auto";
  lineHeight: { unit: "auto" | "px" | "percent"; value: number };
  spacing: number; indent: number; list: "none" | "bullet" | "number";
}

The text of a node is the concatenation of its runs; "\n" starts a new paragraph. style is the default for all runs and each run may override any part of it. With autoResize: "height" the width is fixed and the height derived; with "width" the node is a single line that grows in both directions. curve sets text on an arc, in degrees of sweep. letterSpacing is in em.

Images and SVG#

docs/DOCUMENT_MODEL.md
interface ImageNode extends BaseNode {
  type: "image";
  assetId: Id;
  fit: "fill" | "fit" | "crop" | "tile";
  crop?: { x: number; y: number; w: number; h: number; zoom?: number };   // normalized 0..1 of source, used when fit="crop"
  cornerRadius: number | [number, number, number, number];
  shape?: "rect" | "ellipse" | { path: string };                         // clipping shape; path is SVG d in 0..1 unit box
  adjust?: ImageAdjust;
  strokes: Stroke[];
  maskAssetId?: Id;             // optional alpha mask image (e.g. result of background removal as separate matte)
}

interface SvgNode extends BaseNode {
  type: "svg";
  assetId?: Id;                 // svg asset
  markup?: string;              // inline sanitized SVG (no script/foreignObject/external refs); one of assetId|markup is required
  recolor?: Record<Color, Color>; // map original -> replacement colors
  strokes?: Stroke[];
}

crop is normalized (0..1) to the source image and used when fit is "crop". shape clips the image to an ellipse or any path in a 0..1 unit box. maskAssetId is an optional alpha matte, such as the result of background removal. SVG markup is always sanitized: no scripts, foreignObject or external references. recolor maps original colors to replacements.

Video and audio#

docs/DOCUMENT_MODEL.md
interface VideoNode extends BaseNode { type: "video"; assetId: Id; trimStartMs: number; trimEndMs?: number; muted: boolean; loop: boolean; volume: number; posterAssetId?: Id; fit: "fill" | "fit" | "crop"; cornerRadius: number; }

interface AudioNode extends BaseNode { type: "audio"; assetId: Id; startMs: number; trimStartMs: number; trimEndMs?: number; volume: number; fadeInMs?: number; fadeOutMs?: number; }

Charts, tables and QR codes#

docs/DOCUMENT_MODEL.md
interface ChartNode extends BaseNode {
  type: "chart";
  chart: "bar" | "column" | "line" | "area" | "pie" | "donut" | "scatter" | "radar" | "funnel" | "stacked-column" | "progress";
  data: { labels: string[]; series: { name: string; values: number[]; color?: Color }[] };
  options: { legend: boolean; grid: boolean; labels: boolean; palette: Color[]; fontFamily: string; fontSize: number; textColor: Color; min?: number; max?: number; unit?: string };
}

interface TableNode extends BaseNode {
  type: "table";
  rows: number; cols: number;
  colWidths: number[]; rowHeights: number[];
  cells: { r: number; c: number; text: string; style?: Partial<TextStyle>; fill?: Fill; align?: "left" | "center" | "right"; colspan?: number; rowspan?: number }[];
  header: boolean; stripe: boolean; borderColor: Color; borderWidth: number; cellPadding: number; defaultStyle: TextStyle;
}

interface QrNode extends BaseNode { type: "qr"; data: string; fg: Color; bg: Color; errorCorrection: "L" | "M" | "Q" | "H"; rounded: boolean; }

Semantic roles#

Open string union; engines treat unknown roles as opaque. Known roles (AI prompts and describe rely on them): background, hero, headline, subhead, body, caption, eyebrow, cta, button, logo, brand-mark, photo, product, product-card, card, badge, price, rating, icon, illustration, decoration, pattern, divider, quote, author, stat, stat-label, list, list-item, nav, footer, header, section, chart, table, qr, contact, date, location, url, watermark, placeholder, slide-title, slide-body, speaker, avatar, form-field.

See semantic roles for how roles are used in the product.

Popular pages

QuickstartGet started
Keyboard shortcutsEditor
The agentAI
Docker Compose quickstartSelf-hosting
Configuration referenceSelf-hosting
Agent API (v1)Developers