# Group nested diagrams

Use groups when nodes belong to a container rather than merely appearing inside a rectangle.

## Create membership and nesting

[`render`](https://atloria.dev/p/grafloria-h7YM7amryF/developer/grafloria-element-core) mounts a spec and returns a [`DiagramInstance`](https://atloria.dev/p/grafloria-h7YM7amryF/developer/grafloria-renderer-instance-diagraminstance). Use its engine to create groups and its model to fit them.

```js
import { render } from '@grafloria/element';
const host = document.querySelector('#diagram');
if (!(host instanceof HTMLElement)) throw new Error('Missing #diagram');
host.style.height = '500px';
const instance = render({ nodes: [{ id: 'n1', position: { x: 100, y: 100 }, label: 'stage 1' }, { id: 'n2', position: { x: 260, y: 100 }, label: 'stage 2' }, { id: 'n3', position: { x: 180, y: 220 }, label: 'retry' }], edges: [{ source: 'n1', target: 'n2' }, { source: 'n1', target: 'n3' }] }, host);
const engine = instance.getEngine();
const diagram = instance.getModel();
void (async () => {
const pipeline = await engine.addGroup({ name: 'Pipeline' });
for (const id of ['n1', 'n2', 'n3']) await engine.addToGroup(pipeline.id, id);
const retryHandler = await engine.addGroup({ name: 'Retry handler' });
await engine.addToGroup(retryHandler.id, 'n3');
pipeline.addMember(retryHandler.id, diagram);
retryHandler.fitToContents(diagram); pipeline.fitToContents(diagram); instance.renderNow();
})();
```

![The mounted diagram shows the nodes inside the fitted group frame.](https://atloriaassets.blob.core.windows.net/assets/a438221c-c1af-407c-b9e6-a56c986db846/d51203a9e3a8505f443d3635b85eed00.png)

The diagram renders nested frames around the members. Fit the inner group first, then the outer group. Give the host a real height; see [Style a diagram](https://atloria.dev/p/grafloria-h7YM7amryF/developer/style-a-diagram).

## Framework bindings

The same engine calls run from every binding. Use the binding's component and initialization callback to obtain the instance.

:::code-group
```ts title="Angular"
import { AfterViewInit, Component, viewChild } from '@angular/core';
import { DiagramCanvasComponent } from '@grafloria/angular';
@Component({ standalone: true, imports: [DiagramCanvasComponent], template: '<grafloria-diagram-canvas [(nodes)]="nodes" [(edges)]="edges" style="display:block;height:100vh" />' })
export class Groups implements AfterViewInit {
  canvas = viewChild.required(DiagramCanvasComponent);
  nodes = [{ id: 'n1', position: { x: 100, y: 100 }, label: 'stage 1' }, { id: 'n2', position: { x: 260, y: 100 }, label: 'stage 2' }, { id: 'n3', position: { x: 180, y: 220 }, label: 'retry' }];
  edges = [{ source: 'n1', target: 'n2' }, { source: 'n1', target: 'n3' }];
  async ngAfterViewInit() { const e = this.canvas().activeEngine(); if (!e) return; const d = e.getDiagram(); if (!d) return; const g = await e.addGroup({ name: 'Pipeline' }); for (const id of ['n1', 'n2', 'n3']) await e.addToGroup(g.id, id); g.fitToContents(d); }
}
```
```tsx title="Qwik"
import { component$, $ } from '@builder.io/qwik';
import { GrafloriaFlow, type DiagramInstance } from '@grafloria/qwik';
const nodes = [{ id: 'n1', position: { x: 100, y: 100 }, label: 'stage 1' }, { id: 'n2', position: { x: 260, y: 100 }, label: 'stage 2' }, { id: 'n3', position: { x: 180, y: 220 }, label: 'retry' }];
const edges = [{ source: 'n1', target: 'n2' }, { source: 'n1', target: 'n3' }];
export default component$(() => <div style={{ height: '100vh' }}><GrafloriaFlow defaultNodes={nodes} defaultEdges={edges} onInit$={$(async (i: DiagramInstance) => { const e = i.getEngine(); const d = e.getDiagram(); if (!d) return; const g = await e.addGroup({ name: 'Pipeline' }); for (const id of ['n1', 'n2', 'n3']) await e.addToGroup(g.id, id); g.fitToContents(d); i.renderNow(); })} /></div>);
```
```vue title="Vue"
<script setup lang="ts">
import { GrafloriaFlow } from '@grafloria/vue';
import type { DiagramInstance } from '@grafloria/vue';
const nodes = [{ id: 'n1', position: { x: 100, y: 100 }, label: 'stage 1' }, { id: 'n2', position: { x: 260, y: 100 }, label: 'stage 2' }, { id: 'n3', position: { x: 180, y: 220 }, label: 'retry' }];
const edges = [{ source: 'n1', target: 'n2' }, { source: 'n1', target: 'n3' }];
async function onInit(i: DiagramInstance) { const e = i.getEngine(); const d = e.getDiagram(); if (!d) return; const g = await e.addGroup({ name: 'Pipeline' }); for (const id of ['n1', 'n2', 'n3']) await e.addToGroup(g.id, id); g.fitToContents(d); }
</script>
<template><div style="height:100vh"><GrafloriaFlow :default-nodes="nodes" :default-edges="edges" @init="onInit" /></div></template>
```
```tsx title="React"
import { GrafloriaFlow } from '@grafloria/react';
import type { DiagramInstance } from '@grafloria/react';
const nodes = [{ id: 'n1', position: { x: 100, y: 100 }, label: 'stage 1' }, { id: 'n2', position: { x: 260, y: 100 }, label: 'stage 2' }, { id: 'n3', position: { x: 180, y: 220 }, label: 'retry' }];
const edges = [{ source: 'n1', target: 'n2' }, { source: 'n1', target: 'n3' }];
export default function Groups() { const onInit = (i: DiagramInstance) => { void (async () => { const e = i.getEngine(); const d = e.getDiagram(); if (!d) return; const g = await e.addGroup({ name: 'Pipeline' }); for (const id of ['n1', 'n2', 'n3']) await e.addToGroup(g.id, id); g.fitToContents(d); i.renderNow(); })(); }; return <div style={{ height: '100vh' }}><GrafloriaFlow defaultNodes={nodes} defaultEdges={edges} onInit={onInit} /></div>; }
```
:::

## Collapse and expand

Call `collapseGroup()` and `expandGroup()` on the engine. Collapse hides members and replaces boundary links with proxy links; expand restores the members and original links.

```ts
import { render } from '@grafloria/element';
import type { DiagramInstance } from '@grafloria/renderer';
async function collapseAndExpand(instance: DiagramInstance) {
  const engine = instance.getEngine();
  const service = await engine.addGroup({ name: 'Service' });
  for (const id of ['n1', 'n2', 'n3']) await engine.addToGroup(service.id, id);
  await engine.collapseGroup(service.id); instance.renderNow();
  await engine.expandGroup(service.id); instance.renderNow();
}
const host = document.querySelector('#collapse-diagram');
if (!(host instanceof HTMLElement)) throw new Error('Missing #collapse-diagram');
host.style.height = '500px';
const instance = render({ nodes: [
  { id: 'n1', position: { x: 100, y: 100 }, label: 'one' },
  { id: 'n2', position: { x: 260, y: 100 }, label: 'two' },
  { id: 'n3', position: { x: 180, y: 220 }, label: 'three' },
] }, host);
void collapseAndExpand(instance);
```

![The mounted diagram shows the service group after the collapse-and-expand sequence completes.](https://atloriaassets.blob.core.windows.net/assets/a438221c-c1af-407c-b9e6-a56c986db846/df7dfb7ff1c5c2a7345d8bc7a67b2398.png)

See the [Sub-flow live demo](https://grafloria.com/demos/grouping/sub-flow.html) for nesting, fitting, and collective dragging, and the [collapse-and-expand demo](https://grafloria.com/demos/grouping/collapse-expand.html) for reversible proxy links.

## Options

| Option | Type | Default | What it does |
|---|---|---|---|
| `mode` | `GroupFitMode` | `exact` | Overrides the fit mode for one call. |
| `deepRecursive` | `boolean` | — | Fits descendants before the containing group. |
| `padding` | `GroupPadding` | — | Adds space around fitted members. |
| `fitMode` | `GroupFitMode` | `exact` | Controls whether fitting grows, shrinks, or replaces the frame. |

## Pitfalls

- Call `undo()` on `instance.getEngine()`, not on `DiagramInstance`. See [Commands, events and undo](https://atloria.dev/p/grafloria-h7YM7amryF/developer/commands-events-and-undo).
- A declarative `layout` prop does not re-run when node data changes; call the engine layout method explicitly. See [How Grafloria works](https://atloria.dev/p/grafloria-h7YM7amryF/developer/how-grafloria-works).
- If a group is decoration only, disable group dragging and membership-on-drop with interaction configuration.

## Related

- [How Grafloria works](https://atloria.dev/p/grafloria-h7YM7amryF/developer/how-grafloria-works)
- [Commands, events and undo](https://atloria.dev/p/grafloria-h7YM7amryF/developer/commands-events-and-undo)
