Skip to content
D
Documentation

Vue quick start

tutorial
2 min readUpdated

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.

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 with :default-nodes and :default-edges; for the shared flow and EdgeSpec overview, see the 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, and call useGrafloria, useSelection, useViewport, and useOnSelectionChange in its Vue setup scope; the callback subscription ends with that scope. For the shared sibling-instance pattern, see the 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 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 only when the store exists. For the shared flow comments and CommentStore pattern, see the 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

Was this page helpful?