group#
A positioned container whose children inherit its transform, opacity, and time window. The fundamental nesting primitive — think SVG <g>, Figma group, or AE pre-comp. Inherits common fields.
interface GroupElement extends BaseElement { type: 'group'; elements: Element[]; }
Fields#
| Field | Type | Default | Description |
|---|---|---|---|
elements | Element[] | required | Child elements. Same shape as the Source's top-level elements array. |
clip | boolean | false | Render children into a layer the size of the group's box and clip anything outside it (CSS overflow: hidden). Requires explicit width/height. |
border_radius | number | 0 | Corner radius (px) for a clipped group — rounds the clip box so children are masked to a rounded rectangle (a rounded card clipping its content). Only meaningful with clip: true; clamped to half the smaller box dimension. |
Semantics#
The whole concept in four rules:
- Coordinate system. A child's
x/yare in the group's local space. The group's anchor sets the local origin — by default the group's top-left corner, so childx: 0, y: 0sits at the group's top-left. - Transforms stack. Rotation, scale, and opacity multiply down the tree — a group with
opacity: 50containing a child withopacity: 80produces an effective40on screen. Same idea for rotation and scale. - Time is relative. A child's
timeis offset by the group'stime. A child whose time + duration exceeds the group's window is clipped (not visible after the group ends). - Tracks are local. Child
trackvalues establish z-order within the group; the group's owntrackdecides where the whole subtree sits relative to its siblings.
Example: animated card#
{ "type": "group", "id": "card", "x": 960, "y": 540, "width": 800, "height": 400, "time": 0.5, "duration": 5, "animations": [ { "type": "scale-in", "duration": 0.5, "easing": "ease-out-back" }, { "type": "fade-out", "duration": 0.3, "time": "end" } ], "elements": [ { "type": "shape", "shape": "rectangle", "x": 400, "y": 200, "width": 800, "height": 400, "fill_color": "#1e293b", "border_radius": 24 }, { "type": "text", "text": "Inside the card.", "x": 400, "y": 200, "font_size": 64, "font_weight": 700, "fill_color": "#ffffff" } ] }
The scale-in animates the whole card (shape + text) together, because the animation is on the group.
Example: lower third#
Reusable "lower third" subtree — title + accent bar — that you can drop anywhere on the timeline by changing the group's time:
{ "type": "group", "x": 540, "y": 950, "width": 800, "height": 120, "time": 2, "duration": 4, "animations": [ { "type": "slide-right-in", "duration": 0.4 }, { "type": "slide-left-out", "duration": 0.3, "time": "end" } ], "elements": [ { "type": "shape", "shape": "rectangle", "x": 0, "y": 0, "width": 8, "height": 120, "fill_color": "#facc15", "x_anchor": "0%" }, { "type": "text", "text": "Ian Scott · Founder", "x": 24, "y": 60, "x_anchor": "0%", "font_family": "Inter", "font_size": 36, "font_weight": 600, "fill_color": "#ffffff", "text_align": "left" } ] }
Notes#
- Nesting depth — groups can contain groups. The runtime imposes no nesting limit, but render cost scales with element count, not depth.
- Empty group —
elements: []is rejected at the schema level; at minimum one child is required. - Why "group" and not "composition"? Both terms exist in motion graphics tools. We picked the lighter one. A future
compositionelement could carry stronger semantics (nested timeline, separate frame rate, render caching) without colliding with this.