# Vue quick start

Mount a typed Grafloria flow in Vue 3, keep its nodes and edges in Vue state, and call the live diagram instance from your component.

Grafloria uses one headless model behind every framework binding: the Vue component converts your specs into the diagram model, while the live instance gives you the rendered diagram and its operations.

## Prerequisites

- Vue 3.4 or later
- A Vite-based Vue application with TypeScript support
- Node.js 20.19 or later when your project loads the packages through Node

## Install the packages

```bash
npm install @grafloria/vue @grafloria/element @grafloria/renderer @grafloria/engine
```

## 1. Define typed graph data

Import [`NodeSpec`](https://bench-grafloria-56.atloria.app/p/bench-grafloria-56-62cg8vR5rF/developer/grafloria-renderer-instance-nodespec#nodespec) and [`EdgeSpec`](https://bench-grafloria-56.atloria.app/p/bench-grafloria-56-62cg8vR5rF/developer/grafloria-renderer-instance-edgespec#edgespec), then keep the graph in refs. The node IDs in the edge endpoints must match the node IDs.

```vue title="App.vue"
<script setup lang="ts">
import { ref } from 'vue';
import { GrafloriaFlow } from '@grafloria/vue';
import type { NodeSpec, EdgeSpec } from '@grafloria/vue';

const nodes = ref<NodeSpec[]>([
  {
    id: 'ingest',
    position: { x: 60, y: 80 },
    size: { width: 180, height: 80 },
    data: { label: 'Ingest' },
  },
  {
    id: 'publish',
    position: { x: 380, y: 80 },
    size: { width: 180, height: 80 },
    data: { label: 'Publish' },
  },
]);

const edges = ref<EdgeSpec[]>([
  { id: 'ingest-to-publish', source: 'ingest', target: 'publish' },
]);
</script>

<template>
  <div style="height: 100vh">
    <GrafloriaFlow v-model:nodes="nodes" v-model:edges="edges" />
  </div>
</template>
```

The mounted flow draws two labelled boxes joined by a link. Dragging a node or adding and removing graph items updates the bound refs through `v-model:nodes` and `v-model:edges`. The wrapper has a resolved height, so the canvas has room to render.

For initial data that the canvas owns after mount, use `:default-nodes="nodes"` and `:default-edges="edges"` instead. Use the controlled `v-model` form when the rest of your application needs the graph data.

## 2. Add the built-in canvas controls

Set the `plugins` prop to `true` on the same mounted component:

```vue
<template>
  <div style="height: 100vh">
    <GrafloriaFlow
      v-model:nodes="nodes"
      v-model:edges="edges"
      :plugins="true"
    />
  </div>
</template>
```

The canvas keeps the graph and adds the shipped minimap, zoom controls, fit control, and dotted background. No stylesheet import is required.

## 3. Capture the live instance

The `init` event supplies a [`DiagramInstance`](https://bench-grafloria-56.atloria.app/p/bench-grafloria-56-62cg8vR5rF/developer/grafloria-renderer-instance-diagraminstance#diagraminstance) after the flow mounts. Keep that reference in the component, then call instance methods from your own UI.

```vue title="App.vue"
<script setup lang="ts">
import { ref } from 'vue';
import { GrafloriaFlow } from '@grafloria/vue';
import type { DiagramInstance, EdgeSpec, NodeSpec } from '@grafloria/vue';

const nodes = ref<NodeSpec[]>([
  {
    id: 'ingest',
    position: { x: 60, y: 80 },
    size: { width: 180, height: 80 },
    data: { label: 'Ingest' },
  },
  {
    id: 'publish',
    position: { x: 380, y: 80 },
    size: { width: 180, height: 80 },
    data: { label: 'Publish' },
  },
]);

const edges = ref<EdgeSpec[]>([
  { id: 'ingest-to-publish', source: 'ingest', target: 'publish' },
]);

let instance: DiagramInstance | null = null;

function onInit(api: DiagramInstance): void {
  instance = api;
}

function fitDiagram(): void {
  instance?.fitView();
}
</script>

<template>
  <div style="height: 100vh">
    <button type="button" @click="fitDiagram">Fit diagram</button>
    <GrafloriaFlow
      v-model:nodes="nodes"
      v-model:edges="edges"
      :plugins="true"
      @init="onInit"
    />
  </div>
</template>
```

The button calls `fitView()` on the mounted diagram and frames both nodes in the canvas. Before `init` fires, `instance` is `null`, so the optional call does nothing; after initialization it operates on the live diagram rather than on a separate model.

## Use sibling UI and diagram specs

Use [`GrafloriaProvider`](https://bench-grafloria-56.atloria.app/p/bench-grafloria-56-62cg8vR5rF/developer/grafloria-vue#grafloriaprovider) when a toolbar or inspector is a sibling of the flow. The composables read the same mounted instance: [`useGrafloria`](https://bench-grafloria-56.atloria.app/p/bench-grafloria-56-62cg8vR5rF/developer/grafloria-vue#usegrafloria) returns it, [`useSelection`](https://bench-grafloria-56.atloria.app/p/bench-grafloria-56-62cg8vR5rF/developer/grafloria-vue#useselection) returns reactive selection state, [`useViewport`](https://bench-grafloria-56.atloria.app/p/bench-grafloria-56-62cg8vR5rF/developer/grafloria-vue#useviewport) returns the camera, and [`useOnSelectionChange`](https://bench-grafloria-56.atloria.app/p/bench-grafloria-56-62cg8vR5rF/developer/grafloria-vue#useonselectionchange) registers a selection callback. [`GrafloriaDiagram`](https://bench-grafloria-56.atloria.app/p/bench-grafloria-56-62cg8vR5rF/developer/grafloria-vue#grafloriadiagram) is the kit-spec host; [`GrafloriaCommentPanel`](https://bench-grafloria-56.atloria.app/p/bench-grafloria-56-62cg8vR5rF/developer/grafloria-vue#grafloriacommentpanel) displays the comment store when comments are enabled.

```vue title="App.vue"
<script setup lang="ts">
import { computed, defineComponent, h } from 'vue';
import {
  GrafloriaCommentPanel,
  GrafloriaDiagram,
  GrafloriaFlow,
  GrafloriaProvider,
  useGrafloria,
  useOnSelectionChange,
  useSelection,
  useViewport,
} from '@grafloria/vue';

const Toolbar = defineComponent({
  setup() {
    const grafloria = useGrafloria();
    const selection = useSelection();
    const viewport = useViewport();
    const commentStore = computed(() => grafloria.value?.getCommentStore() ?? null);

    useOnSelectionChange(() => {
      // Update application state here when the selection changes.
    });

    return () => h('aside', [
      h('p', `Selected: ${selection.value.nodes.length}; zoom: ${viewport.value.zoom}`),
      commentStore.value
        ? h(GrafloriaCommentPanel, { store: commentStore.value })
        : null,
    ]);
  },
});

const nodes = [
  { id: 'start', position: { x: 60, y: 80 }, data: { label: 'Start' } },
  { id: 'finish', position: { x: 300, y: 80 }, data: { label: 'Finish' } },
];
const edges = [{ id: 'start-to-finish', source: 'start', target: 'finish' }];
const spec = JSON.stringify({ nodes, edges });
</script>

<template>
  <GrafloriaProvider>
    <div style="height: 500px">
      <GrafloriaFlow :default-nodes="nodes" :default-edges="edges" :comments="true" />
      <Toolbar />
    </div>
  </GrafloriaProvider>
  <div style="height: 400px">
    <GrafloriaDiagram :spec="spec" />
  </div>
</template>
```

![The page shows the flow canvas with its sibling selection and zoom readout, comment panel area, and a second diagram canvas below.](https://atloriaassets.blob.core.windows.net/assets/a438221c-c1af-407c-b9e6-a56c986db846/72fb5857e3e047ea6dbe00b36ddf47b4.png)

The provider makes the flow instance available to sibling tools; the selection count and zoom value update as the user interacts with the flow. The comment panel receives the flow's store, while the second canvas renders the separate diagram spec.

## What you have at the end

Your Vue component now renders a typed, editable two-node flow, keeps graph changes in Vue refs, and holds the live instance for operations such as `fitView()`, `renderNow()`, `getModel()`, and `getEngine()`.

Try the [Vue 3 in 10 minutes tutorial](https://grafloria.com/learn/vue/) and its [live Vue demos](https://grafloria.com/demos-vue/). Next, read [How Grafloria works](https://bench-grafloria-56.atloria.app/p/bench-grafloria-56-62cg8vR5rF/developer/how-grafloria-works) for the model and instance layers, or [Customize nodes](https://bench-grafloria-56.atloria.app/p/bench-grafloria-56-62cg8vR5rF/developer/customize-nodes) for Vue slot-defined nodes.
