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.
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).
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.
bleedextends the printable area outside the page for print export.gridandguidesare 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.
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#
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#
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#
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#
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#
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.