# Vue quick start

Render a flow, connect sibling UI to its live instance, and mount the diagram and comments components in a Vue 3 app.

One headless model drives every framework binding, so you can learn the model once and use the same diagram behavior across frameworks.

## Prerequisites

Use Vue 3.4 or later; for the shared engine, renderer, and element package setup, see the [JavaScript quick start](https://bench-grafloria-6l.atloria.app/p/bench-grafloria-6l-dqcsEdUbfS/developer/javascript-quick-start).

Install the binding and its peers:

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

## 1. Render a flow

In Vue, bind typed initial arrays to [`GrafloriaFlow`](https://bench-grafloria-6l.atloria.app/p/bench-grafloria-6l-dqcsEdUbfS/developer/grafloria-vue#grafloriaflow) with `:default-nodes` and `:default-edges`; for the shared flow and [`EdgeSpec`](https://bench-grafloria-6l.atloria.app/p/bench-grafloria-6l-dqcsEdUbfS/developer/grafloria-renderer-instance-edgespec#edgespec) overview, see the [React quick start](https://bench-grafloria-6l.atloria.app/p/bench-grafloria-6l-dqcsEdUbfS/developer/react-quick-start). Give the flow's parent a height because the canvas fills that parent.

Create `src/App.vue`:

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

const nodes: 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: EdgeSpec[] = [
  { id: 'ingest-to-publish', source: 'ingest', target: 'publish' },
];
</script>

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

The mounted canvas shows Ingest connected to Publish, with the minimap, zoom/fit controls, and background grid enabled by `plugins`. For application-owned state, bind `v-model:nodes` and `v-model:edges` instead of using the `default…` props.

## 2. Reach the instance from a sibling

Place the sibling panel under [`GrafloriaProvider`](https://bench-grafloria-6l.atloria.app/p/bench-grafloria-6l-dqcsEdUbfS/developer/grafloria-vue#grafloriaprovider), and call [`useGrafloria`](https://bench-grafloria-6l.atloria.app/p/bench-grafloria-6l-dqcsEdUbfS/developer/grafloria-vue#usegrafloria), [`useSelection`](https://bench-grafloria-6l.atloria.app/p/bench-grafloria-6l-dqcsEdUbfS/developer/grafloria-vue#useselection), [`useViewport`](https://bench-grafloria-6l.atloria.app/p/bench-grafloria-6l-dqcsEdUbfS/developer/grafloria-vue#useviewport), and [`useOnSelectionChange`](https://bench-grafloria-6l.atloria.app/p/bench-grafloria-6l-dqcsEdUbfS/developer/grafloria-vue#useonselectionchange) in its Vue setup scope; the callback subscription ends with that scope. For the shared sibling-instance pattern, see the [React quick start](https://bench-grafloria-6l.atloria.app/p/bench-grafloria-6l-dqcsEdUbfS/developer/react-quick-start).

Create `src/App.vue` with a sibling inspector component declared in the same file. The child component calls the composables from its own setup scope, under the provider:

```vue
<script setup lang="ts">
import { defineComponent, h, ref } from 'vue';
import { GrafloriaFlow, GrafloriaProvider } from '@grafloria/vue';
import type { EdgeSpec, NodeSpec } from '@grafloria/vue';
import {
  useGrafloria,
  useOnSelectionChange,
  useSelection,
  useViewport,
} from '@grafloria/vue';

const nodes: 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: EdgeSpec[] = [
  { id: 'ingest-to-publish', source: 'ingest', target: 'publish' },
];

const InspectorPanel = defineComponent({
  setup() {
    const diagram = useGrafloria();
    const selection = useSelection();
    const viewport = useViewport();
    const latestChange = ref('No selection changes yet');

    useOnSelectionChange((change) => {
      latestChange.value = `${change.nodes.length} nodes, ${change.edges.length} edges`;
    });

    return () =>
      h('aside', { style: 'width: 220px; padding: 16px' }, [
        h('p', `${selection.value.nodes.length} nodes selected`),
        h('p', `Zoom: ${viewport.value.zoom.toFixed(2)}`),
        h('p', `Latest change: ${latestChange.value}`),
        h(
          'button',
          { type: 'button', onClick: () => diagram.value?.fitView() },
          'Fit diagram',
        ),
      ]);
  },
});
</script>

<template>
  <GrafloriaProvider>
    <div style="display: flex; height: 480px">
      <div style="flex: 1; min-width: 0">
        <GrafloriaFlow :default-nodes="nodes" :default-edges="edges" />
      </div>
      <InspectorPanel />
    </div>
  </GrafloriaProvider>
</template>
```

## 3. Render a diagram spec

Use [`GrafloriaDiagram`](https://bench-grafloria-6l.atloria.app/p/bench-grafloria-6l-dqcsEdUbfS/developer/grafloria-vue#grafloriadiagram) when you have a diagram spec instead of flow props. Its `spec` accepts a diagram spec object or a JSON string; this example uses a JSON string with nodes and edges.

Create `src/SpecDiagram.vue`:

```vue
<script setup lang="ts">
import { GrafloriaDiagram } from '@grafloria/vue';

const spec = JSON.stringify({
  nodes: [
    {
      id: 'draft',
      position: { x: 70, y: 90 },
      size: { width: 170, height: 70 },
      data: { label: 'Draft' },
    },
    {
      id: 'review',
      position: { x: 340, y: 90 },
      size: { width: 170, height: 70 },
      data: { label: 'Review' },
    },
  ],
  edges: [{ id: 'draft-to-review', source: 'draft', target: 'review' }],
});
</script>

<template>
  <div style="height: 400px">
    <GrafloriaDiagram :spec="spec" />
  </div>
</template>
```

The component mounts a diagram with Draft connected to Review. Changing the spec's value replaces the mounted diagram; an equal value does not trigger a replacement.

## 4. Add a comment panel

In Vue, capture the store in a nullable `shallowRef` from the flow's `@init` handler, then use `v-if` to mount [`GrafloriaCommentPanel`](https://bench-grafloria-6l.atloria.app/p/bench-grafloria-6l-dqcsEdUbfS/developer/grafloria-vue#grafloriacommentpanel) only when the store exists. For the shared flow comments and [`CommentStore`](https://bench-grafloria-6l.atloria.app/p/bench-grafloria-6l-dqcsEdUbfS/developer/grafloria-engine-comments-commentstore#commentstore) pattern, see the [React quick start](https://bench-grafloria-6l.atloria.app/p/bench-grafloria-6l-dqcsEdUbfS/developer/react-quick-start).

Create `src/CommentsExample.vue`:

```vue
<script setup lang="ts">
import { shallowRef } from 'vue';
import { GrafloriaCommentPanel, GrafloriaFlow } from '@grafloria/vue';
import type { DiagramInstance, EdgeSpec, NodeSpec } from '@grafloria/vue';
import type { CommentStore } from '@grafloria/engine';

const store = shallowRef<CommentStore | null>(null);

const nodes: NodeSpec[] = [
  { id: 'design', position: { x: 40, y: 100 }, size: { width: 150, height: 66 }, data: { label: 'Design' } },
  { id: 'review', position: { x: 260, y: 100 }, size: { width: 150, height: 66 }, data: { label: 'Review' } },
  { id: 'ship', position: { x: 480, y: 100 }, size: { width: 150, height: 66 }, data: { label: 'Ship' } },
];

const edges: EdgeSpec[] = [
  { id: 'design-to-review', source: 'design', target: 'review' },
  { id: 'review-to-ship', source: 'review', target: 'ship' },
];

function onInit(instance: DiagramInstance): void {
  const comments = instance.getCommentStore();
  if (!comments) return;

  const threadId = comments.createThread(
    { kind: 'node', id: 'review' },
    'Can we tighten the review checklist?',
  );
  comments.reply(threadId, 'I will update it before handoff.');
  store.value = comments;
}
</script>

<template>
  <div style="display: flex; height: 400px">
    <div style="flex: 1; min-width: 0">
      <GrafloriaFlow
        :default-nodes="nodes"
        :default-edges="edges"
        :comments="true"
        @init="onInit"
      />
    </div>
    <aside v-if="store" style="width: 300px; overflow: auto">
      <GrafloriaCommentPanel :store="store" />
    </aside>
  </div>
</template>
```

The initial view contains the Design → Review → Ship flow and a comment thread attached to Review. `getCommentStore()` returns `null` when comments are not enabled, so the panel renders only after the enabled flow supplies its store.

## What you have

You can mount a typed flow, read live instance state from a sibling component, render a JSON diagram spec, and attach the comments UI to a flow's comment store.

## Where to go next

- [State and data flow in Vue](https://bench-grafloria-6l.atloria.app/p/bench-grafloria-6l-dqcsEdUbfS/developer/instance-and-data-flow) explains controlled and uncontrolled flow data and saving diagrams.
- [Lay out diagrams automatically](https://bench-grafloria-6l.atloria.app/p/bench-grafloria-6l-dqcsEdUbfS/developer/lay-out-diagrams) covers layout choices.
- See the [live drag-and-undo demo](https://grafloria.com/demos-vue/#/interaction/drag-undo) and [live comments demo](https://grafloria.com/demos-vue/#/collab/comments).
