# Resize and size nodes

Set a node's starting dimensions, constrain a user's resize gesture, or let the renderer fit a node to its label. This page focuses on node sizing; for mounting patterns and framework-specific data bindings, see [Build runnable workflows](https://bench-grafloria-6l.atloria.app/p/bench-grafloria-6l-dqcsEdUbfS/developer/build-runnable-workflows). These steps cover JavaScript, Angular, React, and Vue; the Qwik binding is outside this page's scope.

## Mount nodes with sizes and sizing rules

The example renders four nodes: one with a declared starting size, one whose resize gesture has minimums and maximums, one with a locked aspect ratio, and one that opts into content-aware sizing. Give the canvas a real height so the rendered diagram has room to appear.

:::code-group
```js title="JavaScript"
import { render } from '@grafloria/element';

const host = document.createElement('div');
host.style.height = '700px';
document.body.append(host);

const diagram = render({
  nodes: [
    {
      id: 'sized',
      position: { x: 120, y: 80 },
      size: { width: 180, height: 90 },
      label: 'Declared size: 180 × 90',
    },
    {
      id: 'resizable',
      position: { x: 120, y: 220 },
      size: { width: 160, height: 100 },
      label: 'Resize me: 80–260 wide',
      metadata: {
        sizing: {
          minWidth: 80,
          minHeight: 60,
          maxWidth: 260,
          maxHeight: 200,
        },
      },
    },
    {
      id: 'ratio',
      position: { x: 420, y: 220 },
      size: { width: 160, height: 100 },
      label: 'Aspect ratio stays at 1.6',
      metadata: { sizing: { aspectLock: true } },
    },
    {
      id: 'auto',
      position: { x: 120, y: 360 },
      size: { width: 60, height: 36 },
      label: 'This long label grows beyond the declared sixty-pixel width',
      metadata: { sizing: { auto: true, padding: 10 } },
    },
  ],
  edges: [],
}, host);
```
```ts title="Angular"
import { Component } from '@angular/core';
import { DiagramCanvasComponent } from '@grafloria/angular';
import type { EdgeSpec, NodeSpec } from '@grafloria/renderer';
import type { LinkModel, NodeModel } from '@grafloria/engine';

@Component({
  standalone: true,
  imports: [DiagramCanvasComponent],
  template: `
    <grafloria-diagram-canvas
      [(nodes)]="nodes"
      [(edges)]="edges"
      style="display:block; height:700px" />
  `,
})
export class AppComponent {
  nodes: readonly (NodeSpec | NodeModel)[] | undefined = [
    {
      id: 'sized',
      position: { x: 120, y: 80 },
      size: { width: 180, height: 90 },
      label: 'Declared size: 180 × 90',
    },
    {
      id: 'resizable',
      position: { x: 120, y: 220 },
      size: { width: 160, height: 100 },
      label: 'Resize me: 80–260 wide',
      metadata: {
        sizing: {
          minWidth: 80,
          minHeight: 60,
          maxWidth: 260,
          maxHeight: 200,
        },
      },
    },
    {
      id: 'ratio',
      position: { x: 420, y: 220 },
      size: { width: 160, height: 100 },
      label: 'Aspect ratio stays at 1.6',
      metadata: { sizing: { aspectLock: true } },
    },
    {
      id: 'auto',
      position: { x: 120, y: 360 },
      size: { width: 60, height: 36 },
      label: 'This long label grows beyond the declared sixty-pixel width',
      metadata: { sizing: { auto: true, padding: 10 } },
    },
  ];
  edges: readonly (EdgeSpec | LinkModel)[] | undefined = [];
}
```
```tsx title="React"
import { GrafloriaFlow } from '@grafloria/react';
import type { EdgeSpec, NodeSpec } from '@grafloria/renderer';

const nodes: NodeSpec[] = [
  {
    id: 'sized',
    position: { x: 120, y: 80 },
    size: { width: 180, height: 90 },
    label: 'Declared size: 180 × 90',
  },
  {
    id: 'resizable',
    position: { x: 120, y: 220 },
    size: { width: 160, height: 100 },
    label: 'Resize me: 80–260 wide',
    metadata: {
      sizing: {
        minWidth: 80,
        minHeight: 60,
        maxWidth: 260,
        maxHeight: 200,
      },
    },
  },
  {
    id: 'ratio',
    position: { x: 420, y: 220 },
    size: { width: 160, height: 100 },
    label: 'Aspect ratio stays at 1.6',
    metadata: { sizing: { aspectLock: true } },
  },
  {
    id: 'auto',
    position: { x: 120, y: 360 },
    size: { width: 60, height: 36 },
    label: 'This long label grows beyond the declared sixty-pixel width',
    metadata: { sizing: { auto: true, padding: 10 } },
  },
];
const edges: EdgeSpec[] = [];

export function App() {
  return (
    <div style={{ height: 700 }}>
      <GrafloriaFlow defaultNodes={nodes} defaultEdges={edges} />
    </div>
  );
}
```
```vue title="Vue"
<script setup lang="ts">
import { GrafloriaFlow } from '@grafloria/vue';
import type { EdgeSpec, NodeSpec } from '@grafloria/renderer';

const nodes: NodeSpec[] = [
  {
    id: 'sized',
    position: { x: 120, y: 80 },
    size: { width: 180, height: 90 },
    label: 'Declared size: 180 × 90',
  },
  {
    id: 'resizable',
    position: { x: 120, y: 220 },
    size: { width: 160, height: 100 },
    label: 'Resize me: 80–260 wide',
    metadata: {
      sizing: {
        minWidth: 80,
        minHeight: 60,
        maxWidth: 260,
        maxHeight: 200,
      },
    },
  },
  {
    id: 'ratio',
    position: { x: 420, y: 220 },
    size: { width: 160, height: 100 },
    label: 'Aspect ratio stays at 1.6',
    metadata: { sizing: { aspectLock: true } },
  },
  {
    id: 'auto',
    position: { x: 120, y: 360 },
    size: { width: 60, height: 36 },
    label: 'This long label grows beyond the declared sixty-pixel width',
    metadata: { sizing: { auto: true, padding: 10 } },
  },
];
const edges: EdgeSpec[] = [];
</script>

<template>
  <div style="height:700px">
    <GrafloriaFlow :default-nodes="nodes" :default-edges="edges" />
  </div>
</template>
```
:::

The first box starts at its declared 180 × 90 size. Select the constrained box to show the renderer's corner and edge resize handles, then drag a handle: its dimensions stay within the declared limits. The adjacent node holds its initial 1.6 width-to-height ratio while you resize it. The bottom node opts into content-aware sizing, so its long label expands it beyond its declared 60-pixel width. The non-auto node remains at its declared dimensions until a user resizes it.

## Choose the sizing fields

Put per-node constraints in `metadata.sizing`. The renderer applies the same constraints to the interactive resize and content-aware sizing paths.

| Option | Type | Default | What it does |
| --- | --- | --- | --- |
| `size` | `{ width: number; height: number }` | No default stated | Declares a node's width and height in its [`NodeSpec`](https://bench-grafloria-6l.atloria.app/p/bench-grafloria-6l-dqcsEdUbfS/developer/grafloria-renderer-instance-nodespec). |
| `metadata.sizing.auto` | `boolean` | Off unless `true` | Opts that node into content-aware sizing to fit its label. |
| `metadata.sizing.minWidth`, `minHeight` | `number` | No per-node constraint; the resizer's global floor still applies | Sets the node's minimum width or height during resizing and auto-sizing. |
| `metadata.sizing.maxWidth`, `maxHeight` | `number` | No per-node limit | Sets the node's maximum width or height during resizing and auto-sizing. |
| `metadata.sizing.aspectLock` | `boolean \| number` | Unlocked | `true` locks to the node's current width-to-height ratio; a positive number locks to that explicit ratio. |
| `metadata.sizing.padding` | `number` | `8` px | Reserves space around the label when auto-sizing. |

The selected-node handles appear for one selected, resizable, unlocked node. A multi-node selection does not provide proportional resize handles. For automatic fitting, only nodes with `metadata.sizing.auto: true` grow; `padding` defaults to 8 pixels unless you set it, as the example does. The auto-sized node's measured dimensions update on the renderer's frame, and the sizing pass uses the same min/max limits and aspect lock as dragging.

## Watch the demos

- [Auto-sizing nodes](https://grafloria.com/demos/nodes/auto-sizing.html) shows an opted-in node fitting its label and growing again when the label changes.
- [Node resize gesture](https://grafloria.com/demos/nodes/node-resize-gesture.html) shows the built-in handles, per-handle cursors, and live min/max/aspect constraints.
- [Node resizer](https://grafloria.com/demos/nodes/node-resizer.html) contrasts the selected-node resizer with a custom resize control.

## Pitfalls

- Register a custom node renderer before mounting the diagram. If a node's type has no renderer when its host mounts, that host stays empty; registering later does not back-fill it. A custom renderer runs at mount, not as a data binding, so update the DOM you own or use a component with its own reactive source when its content must change.
- Give the canvas a parent with a resolved height. A canvas inside a zero-height wrapper has no visible area to draw into.

## Related

- [Lay out diagrams](https://bench-grafloria-6l.atloria.app/p/bench-grafloria-6l-dqcsEdUbfS/developer/lay-out-diagrams)
- [Build runnable workflows](https://bench-grafloria-6l.atloria.app/p/bench-grafloria-6l-dqcsEdUbfS/developer/build-runnable-workflows)
