Skip to main content

Containers, Groups & Zones

Flat canvases stop working once a diagram needs structure. VizCraft specs support three structural node types:

  • type: 'group' — a container frame that owns its children. It auto-sizes to hug them, moves them with it, and can collapse.
  • type: 'zone' — a dashed, non-owning logical region ("these things happen in the browser") that encloses members without re-parenting them.
  • type: 'note' — a sticky-note annotation (see Semantic kinds & legends).

Groups: true parent/child containment

Declare a group and point children at it with parent. Children do not need coordinates — the group lays them out and hugs the result plus padding (default 24). A label renders in a header strip that never overlaps children.

const builder = fromSpec({
view: { width: 640, height: 300 },
nodes: [
{ id: 'github', label: 'Github', type: 'group', x: 200, y: 150 },
{ id: 'gha', label: 'Github Actions', parent: 'github' },
{ id: 'hooks', label: 'Webhooks', parent: 'github' },
{ id: 'amplify', label: 'AWS Amplify', x: 500, y: 150 },
],
edges: [{ from: 'hooks', to: 'amplify', label: 'trigger' }],
});

Adding or removing a child reflows the frame automatically. Children are linked via parentId in the built scene, so moving the group at runtime moves all descendants.

Group-specific options:

OptionDefaultEffect
padding24Inner padding around the children
labelPlacement'top''top', 'top-left', or 'center'
layoutautoLayout engine for the children (see below)
direction'TB'Stack/layer direction ('TB' or 'LR')
spacing24Gap between children
collapsedfalseStart collapsed (see below)

Groups nest recursively

Groups can contain groups — each level hugs its own content:

nodes: [
{ id: 'repo', label: 'Repo Root', type: 'group' },
{ id: 'apps', label: 'App Modules', type: 'group', parent: 'repo' },
{ id: 'mf', label: 'build.config.ts', parent: 'apps' },
{ id: 'pkg', label: 'package.json', parent: 'apps' },
],

Zones: non-owning logical regions

Zones group things visually without claiming ownership. They render as a dashed, faintly tinted region behind real nodes, with the label in a corner. Membership is explicit — put zone: '<zoneId>' on member nodes — or geometric, by pinning the zone's own x/y/width/height.

nodes: [
{ id: 'browser', label: 'Browser', type: 'zone' },
{ id: 'shell', label: 'Portal Shell', x: 180, y: 130, zone: 'browser' },
{ id: 'menu', label: 'Nav Menu', x: 180, y: 220, zone: 'browser' },
{ id: 'api', label: 'API Gateway', x: 500, y: 175 },
],

Zones and groups coexist freely: a zone can span parts of two groups because it never re-parents its members.

Collapse & expand

Set collapsed: true on a group to render it as a single summary node with a child-count badge. Edges that pointed at hidden children re-terminate on the group boundary (and duplicates are merged). Click the group to expand it — and click again to collapse.

nodes: [
{ id: 'repo', label: 'Repo Root', type: 'group', collapsed: true },
{ id: 'a', label: 'apps/web', parent: 'repo' },
{ id: 'b', label: 'packages/ui', parent: 'repo' },
{ id: 'ci', label: 'CI Pipeline', x: 500, y: 150 },
],
edges: [
{ from: 'a', to: 'ci' },
{ from: 'b', to: 'ci' },
],

Focus: trace one path

focus: '<nodeId>' dims everything not connected to the chosen node or group (its descendants, ancestors, and direct edge neighbours stay visible):

const builder = fromSpec({
view: { width: 640, height: 260 },
focus: 'registry',
nodes: [
/* … */
],
edges: [
/* … */
],
});

Backward compatibility

Flat specs — every node pinned with x/y, none of the fields above — render exactly as they always have. The container features only activate when you use them.