# Add nodes from palettes

Build a small palette beside a mounted diagram; choosing a shape adds a labelled node to the live model in the next available grid position.

Use this pattern when your app owns a short list of node types and wants readers to add them without editing diagram data directly. The framework bindings mount a canvas; its instance reaches the engine, which adds each selected node to the diagram.

## Build a palette

Start with an empty diagram. In JavaScript, React and Vue, keep the live [`DiagramInstance`](https://bench-grafloria-6l.atloria.app/p/bench-grafloria-6l-dqcsEdUbfS/developer/grafloria-renderer-instance-diagraminstance#diagraminstance) from the component's init callback and call `getEngine().addNode()` from each palette button. Angular gets its active engine from the canvas component when a button is clicked; Qwik updates controlled node data. The engine returns a promise for the node it adds; each sample places new nodes in a grid so their labels stay visible beside previous choices.

Unlike a workflow that starts from a fixed graph, each palette choice adds one labelled `rect` at the next grid position; see [Build runnable workflows](https://bench-grafloria-6l.atloria.app/p/bench-grafloria-6l-dqcsEdUbfS/developer/build-runnable-workflows) for the framework-specific mounting patterns.

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

const root = document.createElement('div');
root.style.cssText = 'display:grid;grid-template-columns:160px 1fr;height:420px';
document.body.append(root);

const palette = document.createElement('div');
palette.style.cssText = 'display:flex;flex-direction:column;gap:8px;padding:12px';
const canvas = document.createElement('div');
canvas.style.height = '420px';
root.append(palette, canvas);

const instance = render({ nodes: [], edges: [] }, canvas);
const kinds = ['Source', 'Filter', 'Sink'];

async function addNode(kind) {
  const count = instance.getModel().getNodes().length;
  await instance.getEngine().addNode({
    type: 'rect',
    position: { x: 40 + (count % 4) * 170, y: 40 + Math.floor(count / 4) * 90 },
    size: { width: 140, height: 64 },
    data: { label: kind },
  });
  instance.renderNow();
}

for (const kind of kinds) {
  const button = document.createElement('button');
  button.textContent = kind;
  button.addEventListener('click', () => { void addNode(kind); });
  palette.append(button);
}
```
```ts title="Angular"
import { Component, viewChild } from '@angular/core';
import { DiagramCanvasComponent } from '@grafloria/angular';
import type { EdgeSpec, NodeSpec } from '@grafloria/renderer';

@Component({
  selector: 'app-node-palette',
  standalone: true,
  imports: [DiagramCanvasComponent],
  template: `
    <div style="display:grid;grid-template-columns:160px 1fr;height:420px">
      <div style="display:flex;flex-direction:column;gap:8px;padding:12px">
        <button type="button" (click)="addNode('Source')">Source</button>
        <button type="button" (click)="addNode('Filter')">Filter</button>
        <button type="button" (click)="addNode('Sink')">Sink</button>
      </div>
      <grafloria-diagram-canvas [nodes]="nodes" [edges]="edges" style="display:block;height:100%" />
    </div>
  `,
})
export class NodePaletteComponent {
  readonly canvas = viewChild.required(DiagramCanvasComponent);
  readonly nodes: NodeSpec[] = [];
  readonly edges: EdgeSpec[] = [];

  async addNode(kind: string): Promise<void> {
    const engine = this.canvas().activeEngine();
    if (!engine) return;
    const diagram = engine.getDiagram() ?? engine.createDiagram();
    const count = diagram.getNodes().length;
    await engine.addNode({
      type: 'rect',
      position: { x: 40 + (count % 4) * 170, y: 40 + Math.floor(count / 4) * 90 },
      size: { width: 140, height: 64 },
      data: { label: kind },
    });
  }
}
```
```tsx title="Qwik"
import { $, component$, useSignal } from '@builder.io/qwik';
import { GrafloriaFlow } from '@grafloria/qwik';
import type { NodeSpec } from '@grafloria/renderer';

export default component$(() => {
  const nodes = useSignal<NodeSpec[]>([]);
  const kinds = ['Source', 'Filter', 'Sink'];

  return (
    <div style={{ display: 'grid', gridTemplateColumns: '160px 1fr', height: '420px' }}>
      <div style={{ display: 'flex', flexDirection: 'column', gap: '8px', padding: '12px' }}>
        {kinds.map((kind) => (
          <button key={kind} type="button" data-kind={kind} onClick$={$((_event: Event, button: HTMLButtonElement) => {
            const selectedKind = button.dataset['kind'];
            if (!selectedKind) return;
            const count = nodes.value.length;
            nodes.value = [...nodes.value, {
              id: `palette-${count + 1}`,
              type: 'rect',
              position: { x: 40 + (count % 4) * 170, y: 40 + Math.floor(count / 4) * 90 },
              size: { width: 140, height: 64 },
              data: { label: selectedKind },
            }];
          })}>
            {kind}
          </button>
        ))}
      </div>
      <div style={{ height: '420px' }}>
        <GrafloriaFlow nodes={nodes.value} defaultEdges={[]} />
      </div>
    </div>
  );
});
```
```tsx title="React"
import { useRef } from 'react';
import { GrafloriaFlow, type DiagramInstance } from '@grafloria/react';

export function NodePalette() {
  const api = useRef<DiagramInstance | null>(null);
  const kinds = ['Source', 'Filter', 'Sink'];

  async function addNode(kind: string): Promise<void> {
    const instance = api.current;
    if (!instance) return;
    const count = instance.getModel().getNodes().length;
    await instance.getEngine().addNode({
      type: 'rect',
      position: { x: 40 + (count % 4) * 170, y: 40 + Math.floor(count / 4) * 90 },
      size: { width: 140, height: 64 },
      data: { label: kind },
    });
    instance.renderNow();
  }

  return (
    <div style={{ display: 'grid', gridTemplateColumns: '160px 1fr', height: 420 }}>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 8, padding: 12 }}>
        {kinds.map((kind) => <button key={kind} type="button" onClick={() => { void addNode(kind); }}>{kind}</button>)}
      </div>
      <div style={{ height: 420 }}>
        <GrafloriaFlow defaultNodes={[]} defaultEdges={[]} onInit={(instance) => { api.current = instance; }} />
      </div>
    </div>
  );
}
```
```vue title="Vue"
<script setup lang="ts">
import { ref } from 'vue';
import { GrafloriaFlow, type DiagramInstance } from '@grafloria/vue';

const api = ref<DiagramInstance | null>(null);
const kinds = ['Source', 'Filter', 'Sink'];

async function addNode(kind: string): Promise<void> {
  const instance = api.value;
  if (!instance) return;
  const count = instance.getModel().getNodes().length;
  await instance.getEngine().addNode({
    type: 'rect',
    position: { x: 40 + (count % 4) * 170, y: 40 + Math.floor(count / 4) * 90 },
    size: { width: 140, height: 64 },
    data: { label: kind },
  });
  instance.renderNow();
}
</script>

<template>
  <div style="display:grid;grid-template-columns:160px 1fr;height:420px">
    <div style="display:flex;flex-direction:column;gap:8px;padding:12px">
      <button v-for="kind in kinds" :key="kind" type="button" @click="addNode(kind)">{{ kind }}</button>
    </div>
    <div style="height:420px">
      <GrafloriaFlow :default-nodes="[]" :default-edges="[]" @init="api = $event" />
    </div>
  </div>
</template>
```
:::

Click a palette button to add a labelled `rect` node. JavaScript, React and Vue call `addNode()` on the mounted instance; Angular calls `addNode()` on its active engine, and Qwik updates its controlled node array. Each addition occupies the next grid position.

## Options that matter

| Option | Type | Default | What it does |
| --- | --- | --- | --- |
| `defaultNodes` | `NodeSpec[]` | Not specified | Seeds the diagram once; the JavaScript, React and Vue samples pass an empty array. Angular and Qwik pass empty controlled node arrays. |
| `defaultEdges` | `EdgeSpec[]` | Not specified | Seeds the diagram's edges; the samples pass an empty array. |
| `onInit` / `onInit$` | `(instance: DiagramInstance) => void` | Not specified | Gives the mounted instance to the JavaScript, React and Vue palette code. Angular reads the active engine from its canvas component; Qwik uses controlled node data. |
| `addNode` config `type` | `string` | Required | Selects the node type; the sample uses `rect`. |
| `addNode` config `position` | `Point` | Required | Sets the node's top-left position in diagram coordinates. |
| `addNode` config `size` | `Size` | Optional | Sets the node's width and height. |
| `addNode` config `data` | `any` | Optional | Carries node data; `data.label` supplies the visible label in these samples. |

## Pitfalls

- If you later replace externally edited node data with `setNodes()` or `loadText()`, those calls reconcile rather than rebuild: ids that remain keep their live objects. Clear the current edges and nodes first, then apply the edited data. See [Edit and copy nodes](https://bench-grafloria-6l.atloria.app/p/bench-grafloria-6l-dqcsEdUbfS/developer/edit-and-copy-nodes) for node-data workflows.
- This palette uses the built-in `rect` node and a data label. For custom-rendered nodes, follow [Create custom nodes](https://bench-grafloria-6l.atloria.app/p/bench-grafloria-6l-dqcsEdUbfS/developer/create-custom-nodes).

## See it running

The [Drag & drop demo](https://grafloria.com/demos/interaction/drag-and-drop.html) shows a drag-and-drop palette whose nodes land at the drop point. For categorized, searchable built-in shapes, see the [Stencil palette demo](https://grafloria.com/demos/diagrams/stencil-palette.html).

## Related

- [JavaScript quick start](https://bench-grafloria-6l.atloria.app/p/bench-grafloria-6l-dqcsEdUbfS/developer/javascript-quick-start)
- [React quick start](https://bench-grafloria-6l.atloria.app/p/bench-grafloria-6l-dqcsEdUbfS/developer/react-quick-start)
- [Vue quick start](https://bench-grafloria-6l.atloria.app/p/bench-grafloria-6l-dqcsEdUbfS/developer/vue-quick-start)
- [Angular quick start](https://bench-grafloria-6l.atloria.app/p/bench-grafloria-6l-dqcsEdUbfS/developer/angular-quick-start)
- [Qwik quick start](https://bench-grafloria-6l.atloria.app/p/bench-grafloria-6l-dqcsEdUbfS/developer/qwik-quick-start)
- [The instance and data flow](https://bench-grafloria-6l.atloria.app/p/bench-grafloria-6l-dqcsEdUbfS/developer/instance-and-data-flow)
- [Command history and edits](https://bench-grafloria-6l.atloria.app/p/bench-grafloria-6l-dqcsEdUbfS/developer/command-history)
