Elements

July 14, 2026 ยท View on GitHub

All elements share one base contract and belong to exactly one presentation document. Authoring inputs may omit IDs; stored elements and Canonical IR entries always have IDs.

Shared fields

interface ElementBaseInput {
  id?: string;
  name?: string;
  box?: Box;
  transform?: TransformInput;
  opacity?: number;
  hidden?: boolean;
  accessibility?: ElementAccessibilityInput;
  action?: ElementAction;
  placeholderKey?: string;
}
FieldDefaultDescription
idgeneratedGlobally stable document element ID.
namegenerated from IDHuman-readable object name.
boxplaceholder box or zero boxPosition and size in points. Required in practice unless a valid placeholder supplies it; connectors derive their box during layout.
transformno rotation or flipsRotation in degrees plus horizontal and vertical flipping.
opacity1Whole-element opacity in the inclusive range 0..1.
hiddenfalseExcludes the element from visible output without deleting it.
accessibilitynon-decorative, no descriptionDescription and decorative status.
actionomittedURL or stable slide-ID navigation.
placeholderKeyomittedBinds the element to a placeholder on the slide's layout.
type ElementAction =
  | { type: "url"; url: string; tooltip?: string }
  | { type: "slide"; slideId: string; tooltip?: string };

Slide actions use slideId, never a page number that can become stale after reordering.

Text

interface TextElementInput extends Omit<ElementBaseInput, "box"> {
  type: "text";
  content: string | TextParagraphInput[];
  textStylePreset?: string;
  box?: Omit<Box, "height"> & { height?: number };
  frame?: TextFrameStyleInput;
}

content is required. A string is an authoring convenience; normalized output always contains paragraphs and runs. See Text and styles for rich text and inheritance.

Text boxes may omit height when x, y, and width are provided. Core estimates the intrinsic height from normalized paragraphs, wrapping, font sizes, line spacing, paragraph spacing, indentation, and text-frame margins. An explicit height remains authoritative. Width is never inferred because it defines the wrapping boundary.

slide.addElement({
  type: "text",
  content: "A simple editable text box",
  box: { x: 48, y: 48, width: 420, height: 48 },
  action: { type: "url", url: "https://example.com" },
});

Image

interface ImageElementInput extends ElementBaseInput {
  type: "image";
  assetId: string;
  fit?: "stretch" | "contain" | "cover" | "crop";
  crop?: Partial<{ left: number; top: number; right: number; bottom: number }>;
}
FieldDefaultNotes
assetIdrequiredMust reference a registered image asset.
fitstretchcontain and cover require source dimensions for geometric resolution.
cropall edges 0Edge fractions use normalized 0..1 coordinates. Used directly with crop; layout derives values for cover.
slide.addElement({
  type: "image",
  assetId: hero.id,
  box: { x: 480, y: 80, width: 400, height: 240 },
  fit: "cover",
  accessibility: { description: "Product dashboard overview" },
});

Shape

interface ShapeElementInput extends ElementBaseInput {
  type: "shape";
  shape: "rect" | "roundRect" | "ellipse" | "triangle" |
    "diamond" | "arrow" | "chevron";
  style?: ShapeStyleInput;
  text?: {
    content: string | TextParagraphInput[];
    textStylePreset?: string;
    frame?: TextFrameStyleInput;
  };
}

Shapes default to no fill and no visible stroke. Supply explicit paint when the shape should be visible.

slide.addElement({
  type: "shape",
  shape: "roundRect",
  box: { x: 48, y: 140, width: 240, height: 100 },
  style: {
    fill: { type: "solid", color: { theme: "accent1" }, opacity: 0.15 },
    stroke: {
      paint: { type: "solid", color: { theme: "accent1" } },
      width: 2,
    },
  },
});

slide.addElement({
  type: "shape",
  shape: "roundRect",
  box: { x: 48, y: 260, width: 240, height: 80 },
  text: { content: "One editable shape", textStylePreset: "title" },
});

Connector

type ConnectorEndpointInput =
  | Point
  | { elementId: string; anchor?: "top" | "right" | "bottom" | "left" | "center" };

interface ConnectorElementInput extends ElementBaseInput {
  type: "connector";
  start: ConnectorEndpointInput;
  end: ConnectorEndpointInput;
  route?: Point[];
  style?: StrokeStyleInput;
}

Connectors can use absolute points or stable element references. route contains intermediate polyline points. Layout resolves references to points and derives the final connector box. Referenced elements must be in the same slide/layout scope.

slide.addElement({
  type: "connector",
  start: { elementId: "source", anchor: "right" },
  end: { elementId: "target", anchor: "left" },
  style: {
    paint: { type: "solid", color: "404040" },
    width: 2,
    endArrow: "triangle",
  },
});

Group

interface GroupElementInput extends ElementBaseInput {
  type: "group";
  coordinateSize: Size;
  children: PresentationElementInput[];
}

box positions and scales the group in its parent coordinate system. coordinateSize defines the local coordinate system used by children. Both dimensions must be positive. Groups may be nested; every descendant receives a globally unique ID.

slide.addElement({
  type: "group",
  box: { x: 500, y: 80, width: 300, height: 180 },
  coordinateSize: { width: 600, height: 360 },
  children: [
    {
      type: "shape",
      shape: "diamond",
      box: { x: 40, y: 40, width: 160, height: 120 },
      style: { fill: { type: "solid", color: { theme: "accent2" } } },
    },
  ],
});

Table

interface TableElementInput extends ElementBaseInput {
  type: "table";
  columns: number[];
  rows: Array<{
    height?: number;
    cells: Array<{
      content: TextContentInput;
      rowSpan?: number;
      colSpan?: number;
      style?: TableCellStyleInput;
    }>;
  }>;
}

Column widths and optional row heights are points. Spans are positive integers and a row may not span more logical columns than the table declares. Core validates structure; automatic measurement, overflow, and pagination belong to Layout.

slide.addElement({
  type: "table",
  box: { x: 48, y: 280, width: 600, height: 160 },
  columns: [240, 180, 180],
  rows: [
    {
      cells: [{
        content: "Quarterly results",
        colSpan: 3,
        style: { fill: { type: "solid", color: { theme: "accent1" } } },
      }],
    },
    { cells: [{ content: "Q1" }, { content: "Q2" }, { content: "Q3" }] },
  ],
});

Validation summary

Element validation covers geometry, opacity, paint and stroke ranges, text structure, image assets/crops, connector references, group coordinate sizes, table spans, placeholder binding, and action targets. Validation returns structured diagnostics rather than stopping at the first cross-document error.