Skip to main content

Layout

Use layout to position elements without adding DOM containers. Coordinates and numeric sizes use stage units.

Place a row or column​

Use hstack for a row and vstack for a column:

import { Card, layout } from "stageroutine";

const a = Card("Client", { width: 24, height: 10 });
const b = Card("Service", { width: 30, height: 10 });

layout.hstack([a, b], { x: "center", y: "center", gap: 6 });

x and y place the top-left of the whole arrangement. Both default to 0. Use "center" on either axis to center the arrangement in the available region. Element origins, scales, and rotations are preserved. Placement uses their visual bounding boxes.

Horizontal stack gaps default to 6 stage units. Vertical stack gaps default to 3 stage units. Use gapX and gapY to set each direction separately.

Choose available space​

A region is a plain rectangle in stage units. It creates no container. The default region is the full stage.

const rightPanel = { x: 84, y: 18, width: 64, height: 60 };

layout.vstack([heading, body], {
region: rightPanel,
x: "center",
y: "center",
gap: 4,
});

Without x or y, placement starts at the region's top-left. Numeric coordinates remain absolute stage coordinates. Regions guide placement; they do not clip, stretch, or shrink content.

Size elements​

Set element widths to control text wrapping. A layout width or height overrides existing element dimensions before measuring. These values size elements, not the available region. Sizes default to each element's existing or intrinsic size.

layout.vstack([heading, body], {
region: rightPanel,
width: 60,
gap: 3,
});

CSS lengths such as "32rem" are also supported. Array dimensions select each outer slot in stacks, or each column (width) and row (height) in grids.

Align and distribute​

Stack align positions slots perpendicular to the main direction:

  • hstack: align their top, center, or bottom edges.
  • vstack: align their left, center, or right edges.

Values are "start" (default), "center", and "end". This layout alignment is separate from the element's own content align.

Use justify to place the stack along its main axis within the region:

layout.hstack([a, b, c], {
region: { x: 12, y: 20, width: 136, height: 50 },
justify: "space-between",
align: "center",
});

justify accepts "start", "center", "end", or "space-between". It defaults to unset and overrides the main-axis x or y when supplied. "space-between" adds equal space between slots while preserving element sizes and the minimum gap. Content can overflow when it exceeds the available space.

To align elements already positioned independently, use layout.align:

layout.align([a, b, c], { axis: "x", align: "end" });

axis defaults to "x"; "y" and "both" are also supported. The reference defaults to their combined bounds. Supply region to use a fixed reference rectangle.

Combine rows and columns​

A nested array switches direction. Elements remain flat stage siblings:

layout.hstack([[heading, body], diagram], {
x: 10,
y: 18,
width: [60, 70],
gap: 6,
});

Here, heading and body form a column. Each gets a width of 60 stage units. The diagram gets 70 stage units. Widths are not percentages. Include gaps and margins when budgeting space.

Align a grid​

Pass rows to layout.grid. Use null for an empty cell:

layout.grid([
[client, gateway, null],
[null, service, database],
], {
x: "center",
y: "center",
gapX: 8,
gapY: 6,
align: "center",
});

Each column uses its widest element; each row uses its tallest element. Grid gaps default to 4 stage units on both axes. align places elements inside cells on both axes (default: "start").

Place relative to another element​

layout.below(caption, diagram, { gap: 4, align: "center" });
layout.rightOf(details, diagram, { gap: 6 });

Use above, below, leftOf, or rightOf. They measure both elements' visual bounds, including origin, scale, and rotation. The gap defaults to 3 stage units; perpendicular alignment defaults to "start".

Arrays preserve reading order and stack sequentially by default. Set stack: false to place each element relative to the same target, with the same perpendicular alignment.

Arrange a circle​

layout.circle([a, b, c], { center: [80, 45], radius: 28 });

The center defaults to the region center; radius defaults to 28 stage units. An element can supply the center; its visual midpoint is used. startAngle defaults to −90 degrees; span defaults to 360 degrees. flatten defaults to 0 (a circle); 1 makes a horizontal line. Elements are centered on orbit points by default. With centerElements: false, each element's origin sits on its orbit point.

Animate a rearrangement​

Layouts calculate positions once. Reapply after content or geometry changes. Placement is immediate by default. Pass animate: true to record transitions with a default duration of 0.6 seconds:

layout.hstack([a, b], { x: "center", y: 30, animate: true });
stage.pause();

For custom timing, use a callback receiving the complete target position, element, and flattened element index:

layout.vstack([a, b], {
animate: (position, element, index) => to(position).delay(index * 0.08),
});

Sizing constraints apply immediately; animate controls placement. Use reactive size transitions explicitly when resizing should animate.

Move a diagram together​

layout.group transforms the current arrangement around its combined visual center:

layout.group([a, b, connector], {
position: [120, 25],
scale: 0.5,
animate: true,
});

scale is a positive relative multiplier (default: 1). It multiplies current element scales, distances, and connector label scales. A second call with scale: 2 reverses a preceding scale: 0.5. An omitted position preserves the current center. An explicit origin selects the pivot in stage units; position then selects that pivot's destination.

Use group(...elements).to(...) to assign the same property targets to each element. Use layout.group when their spatial arrangement should transform together.

Add dividers​

Stacks and grids can create gutter dividers with rule: true. Add the returned elements to the scene:

const dividers = layout.hstack([a, b], { gap: 6, rule: true });
stage.scene("Comparison").with(a, b, ...dividers);

Pass a rule options object to customize its appearance.

For arc, Bézier, and spiral routes, see Circular movement.